Skip to content

Commit

Permalink
introduce ability to define image reference as {name,tag,digest}
Browse files Browse the repository at this point in the history
Signed-off-by: Nicolas De Loof <[email protected]>
  • Loading branch information
ndeloof committed Oct 11, 2024
1 parent a59035a commit e3872a6
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 14 deletions.
2 changes: 1 addition & 1 deletion cli/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ func TestProjectFromSetOfFiles(t *testing.T) {
assert.NilError(t, err)
service, err := p.GetService("simple")
assert.NilError(t, err)
assert.Equal(t, service.Image, "haproxy")
assert.Equal(t, service.Image, types.Image("haproxy"))
}

func TestProjectComposefilesFromSetOfFiles(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion loader/include_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ services:
options.SetProjectName("project", true)
})
assert.NilError(t, err)
assert.Equal(t, p.Services["included"].Image, "alpine")
assert.Equal(t, p.Services["included"].Image, types.Image("alpine"))
}

func createFile(t *testing.T, rootDir, content, fileName string) string {
Expand Down
20 changes: 17 additions & 3 deletions loader/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ services:
}, actual.Extensions))
assert.Check(t, is.Len(actual.Services, 2))
service := actual.Services["foo"]
assert.Check(t, is.Equal("busybox", service.Image))
assert.Check(t, is.Equal(service.Image, types.Image("busybox")))

assert.Check(t, is.DeepEqual(types.Extensions{
"x-foo": "bar",
Expand Down Expand Up @@ -2720,7 +2720,7 @@ services:
options.ResolvePaths = true
})
assert.NilError(t, err)
assert.Equal(t, p.Services["imported"].Image, "overridden")
assert.Equal(t, p.Services["imported"].Image, types.Image("overridden"))
}

func TestLoadDependsOnCycle(t *testing.T) {
Expand Down Expand Up @@ -2921,7 +2921,7 @@ services:
`)
assert.NilError(t, err)
assert.Equal(t, project.Services["test"].Image, "nginx:override")
assert.Equal(t, project.Services["test"].Image, types.Image("nginx:override"))
}

func TestLoadDevelopConfig(t *testing.T) {
Expand Down Expand Up @@ -3555,3 +3555,17 @@ services:
},
})
}

func TestLoadImageReference(t *testing.T) {
p, err := loadYAML(`
name: load-image-reference
services:
test:
image:
name: registry.com/foo
tag: bar
digest: sha256:1234567890123456789012345678901234567890123456789012345678901234
`)
assert.NilError(t, err)
assert.Equal(t, p.Services["test"].Image, types.Image("registry.com/foo:bar@sha256:1234567890123456789012345678901234567890123456789012345678901234"))
}
20 changes: 19 additions & 1 deletion schema/compose-spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@
},
"healthcheck": {"$ref": "#/definitions/healthcheck"},
"hostname": {"type": "string"},
"image": {"type": "string"},
"image": {"$ref": "#/definitions/imageReference"},
"init": {"type": ["boolean", "string"]},
"ipc": {"type": "string"},
"isolation": {"type": "string"},
Expand Down Expand Up @@ -465,6 +465,24 @@
"additionalProperties": false
},

"imageReference": {
"id": "#/definitions/imageReference",
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"name": {"type": "string"},
"tag": {"type": "string"},
"digest": {"type": "string"}
},
"required": ["name"],
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}
]
},

"healthcheck": {
"id": "#/definitions/healthcheck",
"type": "object",
Expand Down
4 changes: 2 additions & 2 deletions types/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ func (p *Project) WithImagesResolved(resolver func(named reference.Named) (godig
if service.Image == "" {
return service, nil
}
named, err := reference.ParseDockerRef(service.Image)
named, err := reference.ParseDockerRef(string(service.Image))
if err != nil {
return service, err
}
Expand All @@ -555,7 +555,7 @@ func (p *Project) WithImagesResolved(resolver func(named reference.Named) (godig
return service, err
}
}
service.Image = named.String()
service.Image = Image(named.String())
return service, nil
})
}
Expand Down
11 changes: 6 additions & 5 deletions types/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,11 @@ func Test_ResolveImages(t *testing.T) {

for _, test := range tests {
service := p.Services["service_1"]
service.Image = test.image
service.Image = Image(test.image)
p.Services["service_1"] = service
p, err := p.WithImagesResolved(resolver)
assert.NilError(t, err)
assert.Equal(t, p.Services["service_1"].Image, test.resolved)
assert.Equal(t, p.Services["service_1"].Image, Image(test.resolved))
}
}

Expand All @@ -218,14 +218,15 @@ func Test_ResolveImages_concurrent(t *testing.T) {
}
for i := 0; i < 1000; i++ {
p.Services[fmt.Sprintf("service_%d", i)] = ServiceConfig{
Image: fmt.Sprintf("image_%d", i),
Image: Image(fmt.Sprintf("image_%d", i)),
}
}
p, err := p.WithImagesResolved(resolver)
assert.NilError(t, err)
for i := 0; i < 1000; i++ {
expected := fmt.Sprintf("docker.io/library/image_%d:latest@%s", i, garfield)
assert.Equal(t, p.Services[fmt.Sprintf("service_%d", i)].Image,
fmt.Sprintf("docker.io/library/image_%d:latest@%s", i, garfield))
Image(expected))
}
}

Expand All @@ -238,7 +239,7 @@ func Test_ResolveImages_concurrent_interrupted(t *testing.T) {
}
for i := 0; i < 10; i++ {
p.Services[fmt.Sprintf("service_%d", i)] = ServiceConfig{
Image: fmt.Sprintf("image_%d", i),
Image: Image(fmt.Sprintf("image_%d", i)),
}
}
_, err := p.WithImagesResolved(resolver)
Expand Down
34 changes: 33 additions & 1 deletion types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import (
"strconv"
"strings"

"github.com/distribution/reference"
"github.com/docker/go-connections/nat"
"github.com/opencontainers/go-digest"
)

// ServiceConfig is the configuration of one service
Expand Down Expand Up @@ -84,7 +86,7 @@ type ServiceConfig struct {
GroupAdd []string `yaml:"group_add,omitempty" json:"group_add,omitempty"`
Hostname string `yaml:"hostname,omitempty" json:"hostname,omitempty"`
HealthCheck *HealthCheckConfig `yaml:"healthcheck,omitempty" json:"healthcheck,omitempty"`
Image string `yaml:"image,omitempty" json:"image,omitempty"`
Image Image `yaml:"image,omitempty" json:"image,omitempty"`
Init *bool `yaml:"init,omitempty" json:"init,omitempty"`
Ipc string `yaml:"ipc,omitempty" json:"ipc,omitempty"`
Isolation string `yaml:"isolation,omitempty" json:"isolation,omitempty"`
Expand Down Expand Up @@ -267,6 +269,36 @@ func (s ServiceConfig) GetDependents(p *Project) []string {
return dependent
}

type Image string

func (i *Image) DecodeMapstructure(value interface{}) error {
switch v := value.(type) {
case string:
*i = Image(v)
case map[string]any:
r := v["name"].(string)
name, err := reference.WithName(r)
if err != nil {
return err
}
if t, ok := v["tag"].(string); ok {
name, err = reference.WithTag(name, t)
if err != nil {
return err
}
}
if d, ok := v["digest"].(string); ok {
name, err = reference.WithDigest(name, digest.Digest(d))
if err != nil {
return err
}
}
*i = Image(reference.FamiliarString(name))
}
return nil

}

// BuildConfig is a type for build
type BuildConfig struct {
Context string `yaml:"context,omitempty" json:"context,omitempty"`
Expand Down

0 comments on commit e3872a6

Please sign in to comment.