diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 486bebc..41f5822 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -22,7 +22,7 @@ jobs: name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.19 + go-version: 1.21 - name: Run Go Tests run: go test ./... -cover -race diff --git a/Dockerfile b/Dockerfile index 3f1001b..78f55fa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # Use the official Golang image to create a build artifact. # This is based on Debian and sets the GOPATH to /go. # https://hub.docker.com/_/golang -FROM golang:1.19 as builder +FROM golang:1.21 as builder # https://stackoverflow.com/questions/36279253/go-compiled-binary-wont-run-in-an-alpine-docker-container-on-ubuntu-host ENV CGO_ENABLED=0 diff --git a/go.mod b/go.mod index a2d0fed..78aada3 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,14 @@ module github.com/score-spec/score-compose -go 1.19 +go 1.21 + +toolchain go1.21.0 require ( github.com/compose-spec/compose-go v1.6.0 github.com/imdario/mergo v0.3.13 github.com/mitchellh/mapstructure v1.5.0 - github.com/score-spec/score-go v0.0.0-20230905115428-131acdd2f5cf + github.com/score-spec/score-go v1.0.2 github.com/spf13/cobra v1.6.0 github.com/stretchr/testify v1.8.0 github.com/tidwall/sjson v1.2.5 diff --git a/go.sum b/go.sum index 7c4ccc3..cdbabb2 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,7 @@ github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb/go.mo github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= @@ -17,7 +18,6 @@ github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -27,12 +27,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= -github.com/score-spec/score-go v0.0.0-20230904154058-9314581aa692 h1:2MjtKoy0SIDOJ0PowkN353IiLLcLhJOCm9tYKhqlI7Y= -github.com/score-spec/score-go v0.0.0-20230904154058-9314581aa692/go.mod h1:3l9mvrtYKzxXDQVcYkQBD3ABTPkTzWhUMYNfGlpctoo= -github.com/score-spec/score-go v0.0.0-20230904160618-260c98499dbf h1:CALrjCOe+mGvsAD1yX8zlV6k8W/OM/1an2mWweB3wW8= -github.com/score-spec/score-go v0.0.0-20230904160618-260c98499dbf/go.mod h1:3l9mvrtYKzxXDQVcYkQBD3ABTPkTzWhUMYNfGlpctoo= -github.com/score-spec/score-go v0.0.0-20230905115428-131acdd2f5cf h1:0Dt+qyYoGTXPPU5Xq/KxLDtQMMA8hX+oJ8EsjFWUajQ= -github.com/score-spec/score-go v0.0.0-20230905115428-131acdd2f5cf/go.mod h1:3l9mvrtYKzxXDQVcYkQBD3ABTPkTzWhUMYNfGlpctoo= +github.com/score-spec/score-go v1.0.2 h1:xuzAIK9vNr1Vi4YcsZIdsmLEkfI826pMg5R+vysdf6A= +github.com/score-spec/score-go v1.0.2/go.mod h1:nt6TOq2Ld9SiH3Fd9NF8tiJ9L7S17OE3FNgCrSet5GQ= github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -56,9 +52,12 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+v golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= +gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= diff --git a/internal/command/run.go b/internal/command/run.go index 4ed90fe..23a32cf 100644 --- a/internal/command/run.go +++ b/internal/command/run.go @@ -157,7 +157,7 @@ func run(cmd *cobra.Command, args []string) error { // Convert SCORE spec // - var spec score.WorkloadSpec + var spec score.Workload if err = loader.MapSpec(&spec, srcMap); err != nil { return fmt.Errorf("validating workload spec: %w", err) } diff --git a/internal/compose/convert.go b/internal/compose/convert.go index 1663fa8..6baadce 100644 --- a/internal/compose/convert.go +++ b/internal/compose/convert.go @@ -17,7 +17,7 @@ import ( ) // ConvertSpec converts SCORE specification into docker-compose configuration. -func ConvertSpec(spec *score.WorkloadSpec) (*compose.Project, ExternalVariables, error) { +func ConvertSpec(spec *score.Workload) (*compose.Project, ExternalVariables, error) { ctx, err := buildContext(spec.Metadata, spec.Resources) if err != nil { return nil, nil, fmt.Errorf("preparing context: %w", err) @@ -31,18 +31,14 @@ func ConvertSpec(spec *score.WorkloadSpec) (*compose.Project, ExternalVariables, } var ports []compose.ServicePortConfig - if len(spec.Service.Ports) > 0 { + if spec.Service != nil && len(spec.Service.Ports) > 0 { ports = []compose.ServicePortConfig{} for _, pSpec := range spec.Service.Ports { var pubPort = fmt.Sprintf("%v", pSpec.Port) - var tgtPort = pSpec.TargetPort - if pSpec.TargetPort == 0 { - tgtPort = pSpec.Port - } ports = append(ports, compose.ServicePortConfig{ Published: pubPort, - Target: uint32(tgtPort), - Protocol: pSpec.Protocol, + Target: uint32(DerefOr(pSpec.TargetPort, pSpec.Port)), + Protocol: DerefOr(pSpec.Protocol, ""), }) } } @@ -56,14 +52,14 @@ func ConvertSpec(spec *score.WorkloadSpec) (*compose.Project, ExternalVariables, if len(cSpec.Volumes) > 0 { volumes = make([]compose.ServiceVolumeConfig, len(cSpec.Volumes)) for idx, vol := range cSpec.Volumes { - if vol.Path != "" { - return nil, nil, fmt.Errorf("can't mount named volume with sub path '%s': %w", vol.Path, errors.New("not supported")) + if vol.Path != nil && *vol.Path != "" { + return nil, nil, fmt.Errorf("can't mount named volume with sub path '%s': %w", *vol.Path, errors.New("not supported")) } volumes[idx] = compose.ServiceVolumeConfig{ Type: "volume", Source: ctx.Substitute(vol.Source), Target: vol.Target, - ReadOnly: vol.ReadOnly, + ReadOnly: DerefOr(vol.ReadOnly, false), } } } diff --git a/internal/compose/convert_test.go b/internal/compose/convert_test.go index 5122074..ed42753 100644 --- a/internal/compose/convert_test.go +++ b/internal/compose/convert_test.go @@ -23,7 +23,7 @@ func TestScoreConvert(t *testing.T) { var tests = []struct { Name string - Source *score.WorkloadSpec + Source *score.Workload Project *compose.Project Vars ExternalVariables Error error @@ -32,24 +32,24 @@ func TestScoreConvert(t *testing.T) { // { Name: "Should convert SCORE to docker-compose spec", - Source: &score.WorkloadSpec{ - Metadata: score.WorkloadMeta{ + Source: &score.Workload{ + Metadata: score.WorkloadMetadata{ Name: "test", }, - Service: score.ServiceSpec{ - Ports: score.ServicePortsSpecs{ - "www": score.ServicePortSpec{ + Service: &score.WorkloadService{ + Ports: score.WorkloadServicePorts{ + "www": score.ServicePort{ Port: 80, - TargetPort: 8080, + TargetPort: Ref(8080), }, - "admin": score.ServicePortSpec{ + "admin": score.ServicePort{ Port: 8080, - Protocol: "UDP", + Protocol: Ref("udp"), }, }, }, - Containers: score.ContainersSpecs{ - "backend": score.ContainerSpec{ + Containers: score.WorkloadContainers{ + "backend": score.Container{ Image: "busybox", Command: []string{ "/bin/sh", @@ -87,7 +87,7 @@ func TestScoreConvert(t *testing.T) { { Published: "8080", Target: 8080, - Protocol: "UDP", + Protocol: "udp", }, }, }, @@ -97,12 +97,12 @@ func TestScoreConvert(t *testing.T) { }, { Name: "Should convert all resources references", - Source: &score.WorkloadSpec{ - Metadata: score.WorkloadMeta{ + Source: &score.Workload{ + Metadata: score.WorkloadMetadata{ Name: "test", }, - Containers: score.ContainersSpecs{ - "backend": score.ContainerSpec{ + Containers: score.WorkloadContainers{ + "backend": score.Container{ Image: "busybox", Variables: map[string]string{ "DEBUG": "${resources.env.DEBUG}", @@ -110,16 +110,16 @@ func TestScoreConvert(t *testing.T) { "DOMAIN_NAME": "${resources.some-dns.domain_name}", "CONNECTION_STRING": "postgresql://${resources.app-db.host}:${resources.app-db.port}/${resources.app-db.name}", }, - Volumes: []score.VolumeMountSpec{ + Volumes: []score.ContainerVolumesElem{ { Source: "${resources.data}", Target: "/mnt/data", - ReadOnly: true, + ReadOnly: Ref(true), }, }, }, }, - Resources: map[string]score.ResourceSpec{ + Resources: map[string]score.Resource{ "env": { Type: "environment", }, @@ -168,24 +168,24 @@ func TestScoreConvert(t *testing.T) { // { Name: "Should report an error for volumes with sub path (not supported)", - Source: &score.WorkloadSpec{ - Metadata: score.WorkloadMeta{ + Source: &score.Workload{ + Metadata: score.WorkloadMetadata{ Name: "test", }, - Containers: score.ContainersSpecs{ - "backend": score.ContainerSpec{ + Containers: score.WorkloadContainers{ + "backend": score.Container{ Image: "busybox", - Volumes: []score.VolumeMountSpec{ + Volumes: []score.ContainerVolumesElem{ { Source: "${resources.data}", - Path: "sub/path", Target: "/mnt/data", - ReadOnly: true, + Path: Ref("sub/path"), + ReadOnly: Ref(true), }, }, }, }, - Resources: map[string]score.ResourceSpec{ + Resources: map[string]score.Resource{ "data": { Type: "volume", }, diff --git a/internal/compose/ref.go b/internal/compose/ref.go new file mode 100644 index 0000000..d5bba71 --- /dev/null +++ b/internal/compose/ref.go @@ -0,0 +1,12 @@ +package compose + +func Ref[k any](input k) *k { + return &input +} + +func DerefOr[k any](input *k, def k) k { + if input == nil { + return def + } + return *input +} diff --git a/internal/compose/templates.go b/internal/compose/templates.go index 66e1173..d5148b8 100644 --- a/internal/compose/templates.go +++ b/internal/compose/templates.go @@ -25,14 +25,14 @@ var ( // templatesContext ia an utility type that provides a context for '${...}' templates substitution type templatesContext struct { meta map[string]interface{} - resources score.ResourcesSpecs + resources score.WorkloadResources // env map is populated dynamically with any refenced variable used by Substitute env map[string]interface{} } // buildContext initializes a new templatesContext instance -func buildContext(metadata score.WorkloadMeta, resources score.ResourcesSpecs) (*templatesContext, error) { +func buildContext(metadata score.WorkloadMetadata, resources score.WorkloadResources) (*templatesContext, error) { var metadataMap = make(map[string]interface{}) if decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ TagName: "json", diff --git a/internal/compose/templates_test.go b/internal/compose/templates_test.go index 03a0caf..32fc4d4 100644 --- a/internal/compose/templates_test.go +++ b/internal/compose/templates_test.go @@ -15,15 +15,15 @@ import ( ) func TestMapVar(t *testing.T) { - var meta = score.WorkloadMeta{ + var meta = score.WorkloadMetadata{ Name: "test-name", } - var resources = score.ResourcesSpecs{ - "env": score.ResourceSpec{ + var resources = score.WorkloadResources{ + "env": score.Resource{ Type: "environment", }, - "db": score.ResourceSpec{ + "db": score.Resource{ Type: "postgres", }, } @@ -51,15 +51,15 @@ func TestMapVar(t *testing.T) { } func TestSubstitute(t *testing.T) { - var meta = score.WorkloadMeta{ + var meta = score.WorkloadMetadata{ Name: "test-name", } - var resources = score.ResourcesSpecs{ - "env": score.ResourceSpec{ + var resources = score.WorkloadResources{ + "env": score.Resource{ Type: "environment", }, - "db": score.ResourceSpec{ + "db": score.Resource{ Type: "postgres", }, }