Skip to content

Commit 2d7e3b0

Browse files
breakup function NormalizeInput
1 parent ab38f95 commit 2d7e3b0

File tree

2 files changed

+353
-64
lines changed

2 files changed

+353
-64
lines changed

cmd/deploy/input.go

Lines changed: 90 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -49,106 +49,132 @@ type inputNormalized struct {
4949
Do func(ctx context.Context, dstRef registry.Reference) (ociregistry.Descriptor, error) `json:"-"`
5050
}
5151

52-
func NormalizeInput(raw inputRaw) (inputNormalized, error) {
53-
var normal inputNormalized
54-
55-
switch raw.Type {
56-
case "":
57-
// TODO is there one of the two types that I might push by hand more often than the other that could be the default when this is unspecified?
58-
return normal, fmt.Errorf("missing type")
59-
60-
case typeManifest, typeBlob:
61-
normal.Type = raw.Type
62-
63-
default:
64-
return normal, fmt.Errorf("unknown type: %s", raw.Type)
65-
}
66-
67-
if raw.Refs == nil {
68-
return normal, fmt.Errorf("missing refs entirely (JSON input glitch?)")
69-
}
70-
if len(raw.Refs) == 0 {
71-
return normal, fmt.Errorf("zero refs specified for pushing (need at least one)")
72-
}
73-
normal.Refs = make([]registry.Reference, len(raw.Refs))
74-
var refsDigest ociregistry.Digest // if any ref has a digest, they all have to have the same digest (and our data has to match)
75-
for i, refString := range raw.Refs {
52+
func normalizeInputRefs(deployType deployType, rawRefs []string) ([]registry.Reference, ociregistry.Digest, error) {
53+
refs := make([]registry.Reference, len(rawRefs))
54+
var commonDigest ociregistry.Digest // if any ref has a digest, they all have to have the same digest (and our data has to match)
55+
for i, refString := range rawRefs {
7656
ref, err := registry.ParseRef(refString)
7757
if err != nil {
78-
return normal, fmt.Errorf("%s: failed to parse ref: %w", refString, err)
58+
return nil, "", fmt.Errorf("%s: failed to parse ref: %w", refString, err)
7959
}
8060

8161
if ref.Digest != "" {
82-
if refsDigest == "" {
83-
refsDigest = ref.Digest
84-
} else if ref.Digest != refsDigest {
85-
return normal, fmt.Errorf("refs digest mismatch in %s: %s", ref, refsDigest)
62+
if commonDigest == "" {
63+
commonDigest = ref.Digest
64+
} else if ref.Digest != commonDigest {
65+
return nil, "", fmt.Errorf("refs digest mismatch in %s: %s", ref, commonDigest)
8666
}
8767
}
8868

89-
if normal.Type == typeBlob && ref.Tag != "" {
90-
return normal, fmt.Errorf("cannot push blobs to a tag: %s", ref)
69+
if deployType == typeBlob && ref.Tag != "" {
70+
return nil, "", fmt.Errorf("cannot push blobs to a tag: %s", ref)
9171
}
9272

93-
normal.Refs[i] = ref
73+
refs[i] = ref
9474
}
95-
debugId := normal.Refs[0]
9675

97-
normal.Lookup = make(map[ociregistry.Digest]registry.Reference, len(raw.Lookup))
98-
var lookupDigest ociregistry.Digest // if we store this out here, we can abuse it later to get the "last" lookup digest (for getting the single key in the case of len(lookup) == 1 without a new loop)
99-
for d, refString := range raw.Lookup {
100-
lookupDigest = ociregistry.Digest(d)
101-
if lookupDigest != "" {
76+
return refs, commonDigest, nil
77+
}
78+
79+
func normalizeInputLookup(rawLookup map[string]string) (map[ociregistry.Digest]registry.Reference, *ociregistry.Digest, error) {
80+
lookup := make(map[ociregistry.Digest]registry.Reference, len(rawLookup))
81+
var digest ociregistry.Digest // if we store this out here, we can abuse it later to get the "last" lookup digest (for getting the single key in the case of len(lookup) == 1 without a new loop)
82+
for d, refString := range rawLookup {
83+
digest = ociregistry.Digest(d)
84+
if digest != "" {
10285
// normal.Lookup[""] is a special case for fallback (where to look for any child object that isn't explicitly referenced)
103-
if err := lookupDigest.Validate(); err != nil {
104-
return normal, fmt.Errorf("%s: lookup key %q invalid: %w", debugId, lookupDigest, err)
86+
if err := digest.Validate(); err != nil {
87+
return nil, nil, fmt.Errorf("lookup key %q invalid: %w", digest, err)
10588
}
10689
}
10790
if ref, err := registry.ParseRef(refString); err != nil {
108-
return normal, fmt.Errorf("%s: failed to parse lookup ref %q: %v", debugId, refString, err)
91+
return nil, nil, fmt.Errorf("failed to parse lookup ref %q: %v", refString, err)
10992
} else {
110-
if ref.Tag != "" && lookupDigest != "" {
93+
if ref.Tag != "" && digest != "" {
11194
//return normal, fmt.Errorf("%s: tag on by-digest lookup ref makes no sense: %s (%s)", debugId, ref, d)
11295
}
11396

114-
if ref.Digest == "" && lookupDigest != "" {
115-
ref.Digest = lookupDigest
97+
if ref.Digest == "" && digest != "" {
98+
ref.Digest = digest
11699
}
117-
if ref.Digest != lookupDigest && lookupDigest != "" {
118-
return normal, fmt.Errorf("%s: digest on lookup ref should either be omitted or match key: %s vs %s", debugId, ref, d)
100+
if digest != "" && ref.Digest != digest {
101+
return nil, nil, fmt.Errorf("digest on lookup ref should either be omitted or match key: %s vs %s", ref, d)
119102
}
120103

121-
normal.Lookup[lookupDigest] = ref
104+
lookup[digest] = ref
122105
}
123106
}
124107

108+
// see notes on "digest" definition
109+
if len(lookup) != 1 {
110+
return lookup, nil, nil
111+
}
112+
if digest == "" && (lookup[""].Digest == "" && lookup[""].Tag == "") {
113+
// if it was a fallback, it needs at least Tag or Digest (or our refs need Digest, so we can infer)
114+
return lookup, nil, nil
115+
}
116+
return lookup, &digest, nil
117+
}
118+
119+
func NormalizeInput(raw inputRaw) (inputNormalized, error) {
120+
var normal inputNormalized
121+
122+
switch raw.Type {
123+
case "":
124+
// TODO is there one of the two types that I might push by hand more often than the other that could be the default when this is unspecified?
125+
return normal, fmt.Errorf("missing type")
126+
127+
case typeManifest, typeBlob:
128+
normal.Type = raw.Type
129+
130+
default:
131+
return normal, fmt.Errorf("unknown type: %s", raw.Type)
132+
}
133+
134+
if raw.Refs == nil {
135+
return normal, fmt.Errorf("missing refs entirely (JSON input glitch?)")
136+
}
137+
if len(raw.Refs) == 0 {
138+
return normal, fmt.Errorf("zero refs specified for pushing (need at least one)")
139+
}
140+
var (
141+
refsDigest ociregistry.Digest
142+
err error
143+
)
144+
normal.Refs, refsDigest, err = normalizeInputRefs(normal.Type, raw.Refs)
145+
if err != nil {
146+
return normal, err
147+
}
148+
149+
debugId := normal.Refs[0] // used for annotating errors from here out
150+
var lookupDigest *ociregistry.Digest
151+
normal.Lookup, lookupDigest, err = normalizeInputLookup(raw.Lookup)
152+
if err != nil {
153+
return normal, fmt.Errorf("%s: %w", debugId, err)
154+
}
155+
125156
if raw.Data == nil || bytes.Equal(raw.Data, []byte("null")) {
126157
// if we have no Data, let's see if we have enough information to infer an object to copy
127158
if lookupRef, ok := normal.Lookup[refsDigest]; refsDigest != "" && ok {
128159
// if any of our Refs had a digest, *and* we have a way to Lookup that digest, that's the one
129-
lookupDigest = refsDigest
160+
lookupDigest = &refsDigest
130161
normal.CopyFrom = &lookupRef
131-
} else if lookupRef, ok := normal.Lookup[lookupDigest]; len(normal.Lookup) == 1 && ok {
162+
} else if lookupDigest != nil {
132163
// if we only had one Lookup entry, that's the one
133-
if lookupDigest == "" {
134-
// if it was a fallback, it needs at least Tag or Digest (or our refs need Digest, so we can infer)
135-
if lookupRef.Digest == "" && lookupRef.Tag == "" {
136-
if refsDigest != "" {
137-
lookupRef.Digest = refsDigest
138-
} else {
139-
return normal, fmt.Errorf("%s: (single) fallback needs digest or tag: %s", debugId, lookupRef)
140-
}
141-
}
142-
}
164+
lookupRef := normal.Lookup[*lookupDigest]
165+
normal.CopyFrom = &lookupRef
166+
} else if lookupRef, ok := normal.Lookup[""]; refsDigest != "" && ok {
167+
lookupDigest = &refsDigest
168+
lookupRef.Digest = refsDigest
143169
normal.CopyFrom = &lookupRef
144170
} else {
145171
// if Lookup has only a single entry, that's the one (but that's our last chance for inferring intent)
146-
return normal, fmt.Errorf("%s: missing data (and lookup is not a single item)", debugId)
172+
return normal, fmt.Errorf("%s: missing data (and lookup is not a single item or fallback with digest or tag)", debugId)
147173
// TODO *technically* it would be fair to have lookup have two items if one of them is the fallback reference, but it doesn't really make much sense to copy an object from one namespace, but to get all its children from somewhere else
148174
}
149175

150-
if lookupDigest == "" && normal.CopyFrom.Digest != "" {
151-
lookupDigest = normal.CopyFrom.Digest
176+
if *lookupDigest == "" && normal.CopyFrom.Digest != "" {
177+
lookupDigest = &normal.CopyFrom.Digest
152178
}
153179

154180
if _, ok := normal.Lookup[""]; !ok {
@@ -157,8 +183,8 @@ func NormalizeInput(raw inputRaw) (inputNormalized, error) {
157183
}
158184

159185
if refsDigest == "" {
160-
refsDigest = lookupDigest
161-
} else if lookupDigest != "" && refsDigest != lookupDigest {
186+
refsDigest = *lookupDigest
187+
} else if *lookupDigest != "" && refsDigest != *lookupDigest {
162188
return normal, fmt.Errorf("%s: copy-by-digest mismatch: %s vs %s", debugId, refsDigest, normal.CopyFrom)
163189
}
164190
} else {

0 commit comments

Comments
 (0)