Skip to content

Commit

Permalink
feat: upgrade to score-go 1.1.0 with increased validation and fixes f…
Browse files Browse the repository at this point in the history
…or deprecated fields

Signed-off-by: Ben Meier <[email protected]>
  • Loading branch information
astromechza committed Feb 27, 2024
1 parent c507418 commit 067d205
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 7 deletions.
3 changes: 2 additions & 1 deletion e2e-tests/resources/outputs/example-04-extras-output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ services:
volumes:
- type: volume
source: data
target: /usr/share/nginx/html
target: /usr/share/nginx/html
read_only: true
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ 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 v1.0.3
github.com/score-spec/score-go v1.1.0
github.com/spf13/cobra v1.6.0
github.com/stretchr/testify v1.8.0
github.com/tidwall/sjson v1.2.5
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +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 v1.0.3 h1:wNbGcY5Ms4FRfr/qtRVP7fojU9HaDPYkTnzNu0N/ga8=
github.com/score-spec/score-go v1.0.3/go.mod h1:nt6TOq2Ld9SiH3Fd9NF8tiJ9L7S17OE3FNgCrSet5GQ=
github.com/score-spec/score-go v1.1.0 h1:63WM1u93NtGgMuPtVZ/UBfzg/BpYuY8sBquaL0BkrXU=
github.com/score-spec/score-go v1.1.0/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=
Expand Down
11 changes: 10 additions & 1 deletion internal/command/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,15 @@ func run(cmd *cobra.Command, args []string) error {
}
}

// Apply upgrades to fix backports or backward incompatible things
if changes, err := schema.ApplyCommonUpgradeTransforms(srcMap); err != nil {
return fmt.Errorf("failed to upgrade spec: %w", err)
} else if len(changes) > 0 {
for _, change := range changes {
log.Printf("Applying upgrade to specification: %s\n", change)
}
}

// Validate SCORE spec
//
if !skipValidation {
Expand Down Expand Up @@ -185,7 +194,7 @@ func run(cmd *cobra.Command, args []string) error {

// Open output file (optional)
//
var dest = io.Writer(os.Stdout)
var dest = cmd.OutOrStdout()
if outFile != "" {
log.Printf("Creating '%s'...\n", outFile)
destFile, err := os.Create(outFile)
Expand Down
172 changes: 172 additions & 0 deletions internal/command/run_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package command

import (
"bytes"
"context"
"os"
"path/filepath"
"testing"

"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)

// executeAndResetCommand is a test helper that runs and then resets a command for executing in another test.
func executeAndResetCommand(ctx context.Context, cmd *cobra.Command, args []string) (string, string, error) {
beforeOut, beforeErr := cmd.OutOrStderr(), cmd.ErrOrStderr()
defer func() {
cmd.SetOut(beforeOut)
cmd.SetErr(beforeErr)
}()

nowOut, nowErr := new(bytes.Buffer), new(bytes.Buffer)
cmd.SetOut(nowOut)
cmd.SetErr(nowErr)
cmd.SetArgs(args)
subCmd, err := cmd.ExecuteContextC(ctx)
subCmd.SetContext(nil)
subCmd.SilenceUsage = false
subCmd.SilenceErrors = false
subCmd.Flags().VisitAll(func(f *pflag.Flag) {
_ = f.Value.Set(f.DefValue)
})
return nowOut.String(), nowErr.String(), err
}

func TestExample(t *testing.T) {
td := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(td, "score.yaml"), []byte(`
apiVersion: score.dev/v1b1
metadata:
name: example-workload-name123
extra-key: extra-value
service:
ports:
port-one:
port: 1000
protocol: TCP
targetPort: 10000
port-two2:
port: 8000
containers:
container-one1:
image: localhost:4000/repo/my-image:tag
command: ["/bin/sh", "-c"]
args: ["hello", "world"]
resources:
requests:
cpu: 1000m
memory: 10Gi
limits:
cpu: "0.24"
memory: 128M
variables:
SOME_VAR: some content here
volumes:
- source: volume-name
target: /mnt/something
readOnly: false
- source: volume-two
target: /mnt/something-else
livenessProbe:
httpGet:
port: 8080
path: /livez
readinessProbe:
httpGet:
host: 127.0.0.1
port: 80
scheme: HTTP
path: /readyz
httpHeaders:
- name: SOME_HEADER
value: some-value-here
container-two2:
image: localhost:4000/repo/my-image:tag
resources:
resource-one1:
metadata:
annotations:
Default-Annotation: this is my annotation
prefix.com/Another-Key_Annotation.2: something else
extra-key: extra-value
type: Resource-One
class: default
params:
extra:
data: here
resource-two2:
type: Resource-Two
`), 0600))
stdout, stderr, err := executeAndResetCommand(context.Background(), rootCmd, []string{"run", "--file", filepath.Join(td, "score.yaml"), "--output", filepath.Join(td, "compose.yaml")})
assert.NoError(t, err)
assert.NotEqual(t, "", stdout)
assert.Equal(t, "", stderr)
rawComposeContent, err := os.ReadFile(filepath.Join(td, "compose.yaml"))
require.NoError(t, err)
var actualComposeContent map[string]interface{}
assert.NoError(t, yaml.Unmarshal(rawComposeContent, &actualComposeContent))
assert.Equal(t, map[string]interface{}{
"services": map[string]interface{}{
"example-workload-name123-container-one1": map[string]interface{}{
"image": "localhost:4000/repo/my-image:tag",
"entrypoint": []interface{}{"/bin/sh", "-c"},
"command": []interface{}{"hello", "world"},
"environment": map[string]interface{}{
"SOME_VAR": "some content here",
},
"ports": []interface{}{
map[string]interface{}{"target": 10000, "published": "1000", "protocol": "tcp"},
map[string]interface{}{"target": 8000, "published": "8000"},
},
"volumes": []interface{}{
map[string]interface{}{"type": "volume", "source": "volume-name", "target": "/mnt/something"},
map[string]interface{}{"type": "volume", "source": "volume-two", "target": "/mnt/something-else"},
},
},
"example-workload-name123-container-two2": map[string]interface{}{
"image": "localhost:4000/repo/my-image:tag",
"network_mode": "service:example-workload-name123-container-one1",
},
},
}, actualComposeContent)
}

func TestVolumeSubPathNotSupported(t *testing.T) {
td := t.TempDir()
assert.NoError(t, os.WriteFile(filepath.Join(td, "score.yaml"), []byte(`
apiVersion: score.dev/v1b1
metadata:
name: example-workload-name123
containers:
container-one1:
image: localhost:4000/repo/my-image:tag
volumes:
- source: volume-name
target: /mnt/something
path: /sub/path
`), 0600))
stdout, stderr, err := executeAndResetCommand(context.Background(), rootCmd, []string{"run", "--file", filepath.Join(td, "score.yaml"), "--output", filepath.Join(td, "compose.yaml")})
assert.EqualError(t, err, "building docker-compose configuration: can't mount named volume with sub path '/sub/path': not supported")
assert.Contains(t, stdout, "Usage:")
assert.Contains(t, stderr, "building docker-compose configuration:")
}

func TestInvalidWorkloadName(t *testing.T) {
td := t.TempDir()
assert.NoError(t, os.WriteFile(filepath.Join(td, "score.yaml"), []byte(`
apiVersion: score.dev/v1b1
metadata:
name: Invalid Name
containers:
container-one1:
image: localhost:4000/repo/my-image:tag
`), 0600))
stdout, stderr, err := executeAndResetCommand(context.Background(), rootCmd, []string{"run", "--file", filepath.Join(td, "score.yaml")})
assert.EqualError(t, err, "validating workload spec: jsonschema: '/metadata/name' does not validate with https://score.dev/schemas/score#/properties/metadata/properties/name/pattern: does not match pattern '^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$'")
assert.Contains(t, stdout, "Usage:")
assert.Contains(t, stderr, "validating workload spec:")
}
7 changes: 6 additions & 1 deletion internal/compose/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"sort"
"strings"

compose "github.com/compose-spec/compose-go/types"
score "github.com/score-spec/score-go/types"
Expand Down Expand Up @@ -43,10 +44,14 @@ func ConvertSpec(spec *score.Workload) (*compose.Project, ExternalVariables, err
ports = []compose.ServicePortConfig{}
for _, pSpec := range spec.Service.Ports {
var pubPort = fmt.Sprintf("%v", pSpec.Port)
var protocol string
if pSpec.Protocol != nil {
protocol = strings.ToLower(string(*pSpec.Protocol))
}
ports = append(ports, compose.ServicePortConfig{
Published: pubPort,
Target: uint32(DerefOr(pSpec.TargetPort, pSpec.Port)),
Protocol: DerefOr(pSpec.Protocol, ""),
Protocol: protocol,
})
}
}
Expand Down
2 changes: 1 addition & 1 deletion internal/compose/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestScoreConvert(t *testing.T) {
},
"admin": score.ServicePort{
Port: 8080,
Protocol: Ref("udp"),
Protocol: Ref(score.ServicePortProtocolUDP),
},
},
},
Expand Down

0 comments on commit 067d205

Please sign in to comment.