Skip to content

Commit afd3426

Browse files
breakup function NormalizeInput
1 parent ab38f95 commit afd3426

File tree

2 files changed

+285
-47
lines changed

2 files changed

+285
-47
lines changed

cmd/deploy/input.go

Lines changed: 68 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -49,79 +49,100 @@ 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+
lookups := make(map[ociregistry.Digest]registry.Reference, len(rawLookup))
81+
var commonDigest 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+
commonDigest = ociregistry.Digest(d)
84+
if commonDigest != "" {
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 := commonDigest.Validate(); err != nil {
87+
return nil, "", fmt.Errorf("lookup key %q invalid: %w", commonDigest, 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, "", fmt.Errorf("failed to parse lookup ref %q: %v", refString, err)
10992
} else {
110-
if ref.Tag != "" && lookupDigest != "" {
93+
if ref.Tag != "" && commonDigest != "" {
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 == "" && commonDigest != "" {
98+
ref.Digest = commonDigest
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 ref.Digest != commonDigest && commonDigest != "" {
101+
return 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+
lookups[commonDigest] = ref
122105
}
123106
}
124107

108+
return lookups, commonDigest, nil
109+
}
110+
111+
func NormalizeInput(raw inputRaw) (inputNormalized, error) {
112+
var normal inputNormalized
113+
var lookupDigest ociregistry.Digest
114+
var refsDigest ociregistry.Digest
115+
var err error
116+
117+
switch raw.Type {
118+
case "":
119+
// 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?
120+
return normal, fmt.Errorf("missing type")
121+
122+
case typeManifest, typeBlob:
123+
normal.Type = raw.Type
124+
125+
default:
126+
return normal, fmt.Errorf("unknown type: %s", raw.Type)
127+
}
128+
129+
if raw.Refs == nil {
130+
return normal, fmt.Errorf("missing refs entirely (JSON input glitch?)")
131+
}
132+
if len(raw.Refs) == 0 {
133+
return normal, fmt.Errorf("zero refs specified for pushing (need at least one)")
134+
}
135+
normal.Refs, refsDigest, err = normalizeInputRefs(normal.Type, raw.Refs)
136+
if err != nil {
137+
return normal, err
138+
}
139+
140+
debugId := normal.Refs[0]
141+
normal.Lookup, lookupDigest, err = normalizeInputLookup(raw.Lookup)
142+
if err != nil {
143+
return normal, fmt.Errorf("%s: %w", debugId, err)
144+
}
145+
125146
if raw.Data == nil || bytes.Equal(raw.Data, []byte("null")) {
126147
// if we have no Data, let's see if we have enough information to infer an object to copy
127148
if lookupRef, ok := normal.Lookup[refsDigest]; refsDigest != "" && ok {

cmd/deploy/input_test.go

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,226 @@ package main
22

33
import (
44
"encoding/json"
5+
"slices"
6+
"strings"
57
"testing"
8+
9+
"cuelabs.dev/go/oci/ociregistry"
10+
"github.com/docker-library/meta-scripts/registry"
611
)
712

13+
func TestNormalizeInputRefs(t *testing.T) {
14+
type want struct {
15+
refs []registry.Reference
16+
digest ociregistry.Digest
17+
}
18+
type fixture struct {
19+
name string
20+
deployType deployType
21+
rawDigests []string
22+
want want
23+
wantErr string
24+
}
25+
for _, x := range []fixture{
26+
{
27+
"happy path",
28+
typeManifest,
29+
[]string{
30+
"localhost:5000/foo@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d",
31+
"localhost:5000/bar:some-tag",
32+
"localhost:5000/baz",
33+
},
34+
want{
35+
[]registry.Reference{
36+
{
37+
Host: "localhost:5000",
38+
Repository: "foo",
39+
Digest: "sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d",
40+
},
41+
{
42+
Host: "localhost:5000",
43+
Repository: "bar",
44+
Tag: "some-tag",
45+
},
46+
{
47+
Host: "localhost:5000",
48+
Repository: "baz",
49+
},
50+
},
51+
"sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d",
52+
},
53+
"",
54+
},
55+
{
56+
"invalid digest",
57+
typeManifest,
58+
[]string{
59+
"localhost:5000/foo@bad_digest",
60+
},
61+
want{},
62+
"failed to parse ref:",
63+
},
64+
{
65+
"invalid host",
66+
typeManifest,
67+
[]string{
68+
"...",
69+
},
70+
want{},
71+
"failed to parse ref:",
72+
},
73+
{
74+
"invalid tag",
75+
typeManifest,
76+
[]string{
77+
"localhost:5000/foo:#",
78+
},
79+
want{},
80+
"failed to parse ref:",
81+
},
82+
{
83+
"invalid reference url",
84+
typeManifest,
85+
[]string{
86+
"localhost:5000/foo?test",
87+
},
88+
want{},
89+
"failed to parse ref:",
90+
},
91+
{
92+
"mismatch digest",
93+
typeManifest,
94+
[]string{
95+
"localhost:5000/foo@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d",
96+
"localhost:5000/foo@sha256:00042f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d",
97+
},
98+
want{},
99+
"refs digest mismatch in ",
100+
},
101+
{
102+
"can't push to blob",
103+
typeBlob,
104+
[]string{
105+
"localhost:5000/foo:tag",
106+
},
107+
want{},
108+
"cannot push blobs to a tag:",
109+
},
110+
} {
111+
t.Run(x.name, func(t *testing.T) {
112+
refs, refsDigest, err := normalizeInputRefs(x.deployType, x.rawDigests)
113+
114+
if x.wantErr != "" {
115+
if err == nil {
116+
t.Fatalf("Expected error not returned: %s", x.wantErr)
117+
}
118+
119+
if !strings.Contains(err.Error(), x.wantErr) {
120+
t.Fatalf("Expected error doesn't match. got:\n'%s',\n\nexpected to contain:\n'%s'", err.Error(), x.wantErr)
121+
}
122+
}
123+
124+
if refsDigest != x.want.digest {
125+
t.Errorf("got:\n%s\n\nexpected:\n%s\n", string(refsDigest), x.want.digest)
126+
}
127+
128+
if !slices.Equal(refs, x.want.refs) {
129+
t.Errorf("Reference doesn't match. got: \n%#v,\n\nexpected:\n%#v", refs, x.want.refs)
130+
}
131+
})
132+
}
133+
}
134+
135+
func TestNormalizeInputLookup(t *testing.T) {
136+
type want struct {
137+
lookups map[ociregistry.Digest]registry.Reference
138+
digest ociregistry.Digest
139+
}
140+
type fixture struct {
141+
name string
142+
rawLookups map[string]string
143+
want want
144+
wantErr string
145+
}
146+
147+
for _, x := range []fixture{
148+
{
149+
"happy path",
150+
map[string]string{
151+
"sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d": "example:foo",
152+
"sha256:0cb474919526d040392883b84e5babb65a149cc605b89b117781ab94e88a5e86": "foo/bar:baz",
153+
"sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e": "localhost:5000/example:bar",
154+
},
155+
want{
156+
map[ociregistry.Digest]registry.Reference{
157+
"sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d": {
158+
Host: "docker.io",
159+
Repository: "library/example",
160+
Tag: "foo",
161+
Digest: "sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d",
162+
},
163+
"sha256:0cb474919526d040392883b84e5babb65a149cc605b89b117781ab94e88a5e86": {
164+
Host: "docker.io",
165+
Repository: "foo/bar",
166+
Tag: "baz",
167+
Digest: "sha256:0cb474919526d040392883b84e5babb65a149cc605b89b117781ab94e88a5e86",
168+
},
169+
"sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e": {
170+
Host: "localhost:5000",
171+
Repository: "example",
172+
Tag: "bar",
173+
Digest: "sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e",
174+
},
175+
},
176+
"sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e",
177+
},
178+
"",
179+
},
180+
{
181+
"bad digest",
182+
map[string]string{
183+
"invalid digest": "example:foo",
184+
},
185+
want{},
186+
"lookup key \"invalid digest\" invalid:",
187+
},
188+
{
189+
"bad reference",
190+
map[string]string{
191+
"sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d": "://#",
192+
},
193+
want{},
194+
"failed to parse lookup ref",
195+
},
196+
} {
197+
t.Run(x.name, func(t *testing.T) {
198+
lookups, lookupDigest, err := normalizeInputLookup(x.rawLookups)
199+
200+
if x.wantErr != "" {
201+
if err == nil {
202+
t.Fatalf("Expected error not returned: %s", x.wantErr)
203+
}
204+
205+
if !strings.Contains(err.Error(), x.wantErr) {
206+
t.Fatalf("Expected error doesn't match. got:\n'%s',\n\nexpected to contain:\n'%s'", err.Error(), x.wantErr)
207+
}
208+
}
209+
210+
if lookupDigest != x.want.digest {
211+
t.Errorf("Expected last digest. got:\n%s\n\nexpected:\n%s\n", lookupDigest, x.want.digest)
212+
}
213+
214+
for digest, ref := range lookups {
215+
if _, ok := x.want.lookups[digest]; !ok {
216+
t.Errorf("digest missing from lookups: %s", digest)
217+
} else if ref != x.want.lookups[digest] {
218+
t.Errorf("Reference doesn't match. got:\n%#v,\n\nexpected:\n%#v", ref, x.want.lookups[digest])
219+
}
220+
}
221+
})
222+
}
223+
}
224+
8225
func TestNormalizeInput(t *testing.T) {
9226
for _, x := range []struct {
10227
name string

0 commit comments

Comments
 (0)