diff --git a/loader/fixtures/test_binary_file b/loader/fixtures/test_binary_file new file mode 100644 index 0000000..d81ee60 --- /dev/null +++ b/loader/fixtures/test_binary_file @@ -0,0 +1 @@ +]RÎŒLªäR 1Œƒ§ç+«‚ \ No newline at end of file diff --git a/loader/loader_test.go b/loader/loader_test.go index c03d272..70eeec6 100644 --- a/loader/loader_test.go +++ b/loader/loader_test.go @@ -89,6 +89,8 @@ containers: --- ${resources.env.APP_CONFIG} noExpand: true + - target: /etc/hello-world/binary + binaryContent: aGVsbG8= volumes: - source: ${resources.data} path: sub/path @@ -105,6 +107,10 @@ containers: httpGet: path: /alive port: 8080 + exec: + command: + - echo + - hello readinessProbe: httpGet: host: "1.1.1.1" @@ -166,6 +172,10 @@ resources: Content: stringRef("---\n${resources.env.APP_CONFIG}\n"), NoExpand: boolRef(true), }, + { + Target: "/etc/hello-world/binary", + BinaryContent: stringRef("aGVsbG8="), + }, }, Volumes: []types.ContainerVolumesElem{ { @@ -186,13 +196,16 @@ resources: }, }, LivenessProbe: &types.ContainerProbe{ - HttpGet: types.HttpProbe{ + HttpGet: &types.HttpProbe{ Path: "/alive", Port: 8080, }, + Exec: &types.ExecProbe{ + Command: []string{"echo", "hello"}, + }, }, ReadinessProbe: &types.ContainerProbe{ - HttpGet: types.HttpProbe{ + HttpGet: &types.HttpProbe{ Host: stringRef("1.1.1.1"), Scheme: schemeRef(types.HttpProbeSchemeHTTPS), Path: "/ready", diff --git a/loader/normalize.go b/loader/normalize.go index 54bc837..1048469 100644 --- a/loader/normalize.go +++ b/loader/normalize.go @@ -15,6 +15,7 @@ package loader import ( + "encoding/base64" "fmt" "os" "path/filepath" @@ -33,9 +34,14 @@ func Normalize(w *types.Workload, baseDir string) error { if err != nil { return fmt.Errorf("embedding file '%s' for container '%s': %w", *f.Source, name, err) } - c.Files[i].Source = nil - c.Files[i].Content = &raw + if utf8.Valid(raw) { + content := string(raw) + c.Files[i].Content = &content + } else { + content := base64.StdEncoding.EncodeToString(raw) + c.Files[i].BinaryContent = &content + } } } } @@ -44,19 +50,15 @@ func Normalize(w *types.Workload, baseDir string) error { } // readFile reads a text file into memory -func readFile(baseDir, path string) (string, error) { +func readFile(baseDir, path string) ([]byte, error) { if !filepath.IsAbs(path) { path = filepath.Join(baseDir, path) } raw, err := os.ReadFile(path) if err != nil { - return "", err - } - - if !utf8.Valid(raw) { - return "", fmt.Errorf("file contains non-utf8 characters") + return nil, err } - return string(raw), nil + return raw, nil } diff --git a/loader/normalize_test.go b/loader/normalize_test.go index 86002fd..2ece464 100644 --- a/loader/normalize_test.go +++ b/loader/normalize_test.go @@ -19,8 +19,9 @@ import ( "io" "testing" - "github.com/score-spec/score-go/types" "github.com/stretchr/testify/assert" + + "github.com/score-spec/score-go/types" ) func TestNormalize(t *testing.T) { @@ -32,7 +33,7 @@ func TestNormalize(t *testing.T) { Error error }{ { - Name: "Embeds source file", + Name: "Embeds source files", Input: &types.Workload{ ApiVersion: "score.dev/v1b1", Metadata: types.WorkloadMetadata{ @@ -47,6 +48,10 @@ func TestNormalize(t *testing.T) { Mode: stringRef("666"), NoExpand: boolRef(true), }, + { + Source: stringRef("./test_binary_file"), + Target: "/etc/hello-world/binary", + }, }, }, }, @@ -65,6 +70,10 @@ func TestNormalize(t *testing.T) { Content: stringRef("Hello World\n"), NoExpand: boolRef(true), }, + { + Target: "/etc/hello-world/binary", + BinaryContent: stringRef("XVLOjEyq5FKgHDGMAYMdp+crq4I="), + }, }, }, }, diff --git a/schema/files/samples/score-full.yaml b/schema/files/samples/score-full.yaml index 55df5c7..0c312b3 100644 --- a/schema/files/samples/score-full.yaml +++ b/schema/files/samples/score-full.yaml @@ -27,24 +27,31 @@ containers: variables: SOME_VAR: some content here files: - - target: /my/file - mode: "0600" - source: file.txt - - target: /my/other/file - content: | - some multiline - content + - target: /my/file + mode: "0600" + source: file.txt + - target: /my/other/file + content: | + some multiline + content + - target: /my/other/binaryfile + binaryContent: ADBgwpA= volumes: - - source: volume-name - target: /mnt/something - path: /sub/path - readOnly: false - - source: volume-two - target: /mnt/something-else + - source: volume-name + target: /mnt/something + path: /sub/path + readOnly: false + - source: volume-two + target: /mnt/something-else livenessProbe: httpGet: port: 8080 path: /livez + exec: + command: + - /bin/curl + - -f + - "http://localhost:8080/livez" readinessProbe: httpGet: host: 127.0.0.1 @@ -52,8 +59,8 @@ containers: scheme: HTTP path: /readyz httpHeaders: - - name: SOME_HEADER - value: some-value-here + - name: SOME_HEADER + value: some-value-here container-two2: image: . resources: @@ -70,6 +77,6 @@ resources: data: here resource-two2: type: Resource-Two - resource.three: + resource-three: type: Type-Three - id: shared-type-three + id: shared-type-three \ No newline at end of file diff --git a/schema/files/score-v1b1.json b/schema/files/score-v1b1.json index 94d8364..879bfc8 100644 --- a/schema/files/score-v1b1.json +++ b/schema/files/score-v1b1.json @@ -235,6 +235,7 @@ "description": "The extra files to mount into the container.", "type": "array", "items": { + "description": "The details of a file to mount in the container. One of 'source', 'content', or 'binaryContent' must be provided.", "type": "object", "required": [ "target" @@ -257,7 +258,11 @@ "minLength": 1 }, "content": { - "description": "The inline content for the file.", + "description": "The inline content for the file. Only supports valid utf-8.", + "type": "string" + }, + "binaryContent": { + "description": "Inline standard-base64 encoded content for the file. Does not support placeholder expansion.", "type": "string" }, "noExpand": { @@ -272,6 +277,12 @@ "content" ] }, + { + "required": [ + "target", + "binaryContent" + ] + }, { "required": [ "target", @@ -338,13 +349,29 @@ }, "containerProbe": { "type": "object", - "required": [ - "httpGet" - ], + "description": "The probe may be defined as either http, command execution, or both. The execProbe should be preferred if the Score implementation supports both types.", "additionalProperties": false, "properties": { "httpGet": { "$ref": "#/$defs/httpProbe" + }, + "exec": { + "$ref": "#/$defs/execProbe" + } + } + }, + "execProbe": { + "description": "An executable health probe.", + "type": "object", + "additionalProperties": false, + "required": ["command"], + "properties": { + "command": { + "description": "The command and arguments to execute within the container.", + "type": "array", + "items": { + "type": "string" + } } } }, diff --git a/schema/schema_test.go b/schema/schema_test.go index ff85cde..a4c40f8 100644 --- a/schema/schema_test.go +++ b/schema/schema_test.go @@ -49,6 +49,9 @@ containers: - target: /etc/hello-world/config.yaml mode: "666" content: "${resources.env.APP_CONFIG}" + - target: /etc/hello-world/binary + mode: "755" + binaryContent: "aGVsbG8=" volumes: - source: ${resources.data} path: sub/path @@ -65,6 +68,10 @@ containers: httpGet: path: /alive port: 8080 + exec: + command: + - echo + - hello readinessProbe: httpGet: path: /ready @@ -599,7 +606,19 @@ func TestSchema(t *testing.T) { Message: "/containers/hello/files/0/source", }, { - Name: "containers.*.files.*.noExpand isset to true", + Name: "containers.*.files.*.binaryContent is bad format", + Src: func() map[string]interface{} { + src := newTestDocument() + var hello = src["containers"].(map[string]interface{})["hello"].(map[string]interface{}) + var file = hello["files"].([]interface{})[0].(map[string]interface{}) + delete(file, "content") + file["binaryContent"] = map[string]interface{}{} + return src + }(), + Message: "/containers/hello/files/0/binaryContent", + }, + { + Name: "containers.*.files.*.noExpand is set to true", Src: func() map[string]interface{} { src := newTestDocument() var hello = src["containers"].(map[string]interface{})["hello"].(map[string]interface{}) @@ -988,17 +1007,29 @@ func TestSchema(t *testing.T) { Message: "/containers/hello/livenessProbe", }, { - Name: "containers.*.livenessProbe is empty", + Name: "containers.*.livenessProbe.exec is nil", Src: func() map[string]interface{} { src := newTestDocument() var hello = src["containers"].(map[string]interface{})["hello"].(map[string]interface{}) - hello["livenessProbe"] = map[string]interface{}{} + hello["livenessProbe"].(map[string]interface{})["exec"] = nil return src }(), - Message: "/containers/hello/livenessProbe", + Message: "/containers/hello/livenessProbe/exec", }, { - Name: "containers.*.livenessProbe.httpGet is not set", + Name: "containers.*.livenessProbe.exec is bad", + Src: func() map[string]interface{} { + src := newTestDocument() + var hello = src["containers"].(map[string]interface{})["hello"].(map[string]interface{}) + hello["livenessProbe"].(map[string]interface{})["exec"] = map[string]interface{}{ + "command": true, + } + return src + }(), + Message: "/containers/hello/livenessProbe/exec/command", + }, + { + Name: "containers.*.livenessProbe.httpGet is nil", Src: func() map[string]interface{} { src := newTestDocument() var hello = src["containers"].(map[string]interface{})["hello"].(map[string]interface{}) @@ -1228,14 +1259,14 @@ func TestSchema(t *testing.T) { Message: "/containers/hello/readinessProbe", }, { - Name: "containers.*.readinessProbe is empty", + Name: "containers.*.readinessProbe.exec is nil", Src: func() map[string]interface{} { src := newTestDocument() var hello = src["containers"].(map[string]interface{})["hello"].(map[string]interface{}) - hello["readinessProbe"] = map[string]interface{}{} + hello["readinessProbe"].(map[string]interface{})["exec"] = nil return src }(), - Message: "/containers/hello/readinessProbe", + Message: "/containers/hello/readinessProbe/exec", }, { Name: "containers.*.readinessProbe.httpGet is not set", @@ -1245,7 +1276,7 @@ func TestSchema(t *testing.T) { hello["readinessProbe"].(map[string]interface{})["httpGet"] = nil return src }(), - Message: "/containers/hello/readinessProbe", + Message: "/containers/hello/readinessProbe/httpGet", }, { Name: "containers.*.readinessProbe.httpGet.path is missing", diff --git a/schema/validate_test.go b/schema/validate_test.go index 6b40628..a7ee05d 100644 --- a/schema/validate_test.go +++ b/schema/validate_test.go @@ -50,6 +50,8 @@ containers: - target: /etc/hello-world/config.yaml mode: "666" content: "${resources.env.APP_CONFIG}" + - target: /etc/hello-world/binary + content: "aGVsbG8=" volumes: - source: ${resources.data} path: sub/path diff --git a/types/types.gen.go b/types/types.gen.go index 5aafa06..60ebd4f 100644 --- a/types/types.gen.go +++ b/types/types.gen.go @@ -6,57 +6,207 @@ import "encoding/json" import "fmt" import "reflect" -// The specification of a Container within the Workload. -type Container struct { - // If specified, overrides the arguments passed to the container entrypoint. - Args []string `json:"args,omitempty" yaml:"args,omitempty" mapstructure:"args,omitempty"` +// UnmarshalJSON implements json.Unmarshaler. +func (j *ContainerVolumesElem) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if v, ok := raw["source"]; !ok || v == nil { + return fmt.Errorf("field source in ContainerVolumesElem: required") + } + if v, ok := raw["target"]; !ok || v == nil { + return fmt.Errorf("field target in ContainerVolumesElem: required") + } + type Plain ContainerVolumesElem + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ContainerVolumesElem(plain) + return nil +} - // If specified, overrides the entrypoint defined in the container image. - Command []string `json:"command,omitempty" yaml:"command,omitempty" mapstructure:"command,omitempty"` +const ServicePortProtocolTCP ServicePortProtocol = "TCP" - // The extra files to mount into the container. - Files []ContainerFilesElem `json:"files,omitempty" yaml:"files,omitempty" mapstructure:"files,omitempty"` +// An executable health probe. +type ExecProbe struct { + // The command and arguments to execute within the container. + Command []string `json:"command" yaml:"command" mapstructure:"command"` +} - // The container image name and tag. - Image string `json:"image" yaml:"image" mapstructure:"image"` +// UnmarshalJSON implements json.Unmarshaler. +func (j *ExecProbe) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if v, ok := raw["command"]; !ok || v == nil { + return fmt.Errorf("field command in ExecProbe: required") + } + type Plain ExecProbe + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ExecProbe(plain) + return nil +} - // The liveness probe for the container. - LivenessProbe *ContainerProbe `json:"livenessProbe,omitempty" yaml:"livenessProbe,omitempty" mapstructure:"livenessProbe,omitempty"` +type HttpProbeHttpHeadersElem struct { + // The HTTP header name. + Name string `json:"name" yaml:"name" mapstructure:"name"` - // The readiness probe for the container. - ReadinessProbe *ContainerProbe `json:"readinessProbe,omitempty" yaml:"readinessProbe,omitempty" mapstructure:"readinessProbe,omitempty"` + // The HTTP header value. + Value string `json:"value" yaml:"value" mapstructure:"value"` +} - // The compute resources for the container. - Resources *ContainerResources `json:"resources,omitempty" yaml:"resources,omitempty" mapstructure:"resources,omitempty"` +// UnmarshalJSON implements json.Unmarshaler. +func (j *HttpProbeHttpHeadersElem) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if v, ok := raw["name"]; !ok || v == nil { + return fmt.Errorf("field name in HttpProbeHttpHeadersElem: required") + } + if v, ok := raw["value"]; !ok || v == nil { + return fmt.Errorf("field value in HttpProbeHttpHeadersElem: required") + } + type Plain HttpProbeHttpHeadersElem + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + if len(plain.Value) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "value", 1) + } + *j = HttpProbeHttpHeadersElem(plain) + return nil +} - // The environment variables for the container. - Variables ContainerVariables `json:"variables,omitempty" yaml:"variables,omitempty" mapstructure:"variables,omitempty"` +type HttpProbeScheme string - // The volumes to mount. - Volumes []ContainerVolumesElem `json:"volumes,omitempty" yaml:"volumes,omitempty" mapstructure:"volumes,omitempty"` +// UnmarshalJSON implements json.Unmarshaler. +func (j *Workload) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if v, ok := raw["apiVersion"]; !ok || v == nil { + return fmt.Errorf("field apiVersion in Workload: required") + } + if v, ok := raw["containers"]; !ok || v == nil { + return fmt.Errorf("field containers in Workload: required") + } + if v, ok := raw["metadata"]; !ok || v == nil { + return fmt.Errorf("field metadata in Workload: required") + } + type Plain Workload + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Workload(plain) + return nil } -type ContainerFilesElem struct { - // The inline content for the file. - Content *string `json:"content,omitempty" yaml:"content,omitempty" mapstructure:"content,omitempty"` +// UnmarshalJSON implements json.Unmarshaler. +func (j *HttpProbeScheme) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_HttpProbeScheme { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_HttpProbeScheme, v) + } + *j = HttpProbeScheme(v) + return nil +} - // The optional file access mode in octal encoding. For example 0600. - Mode *string `json:"mode,omitempty" yaml:"mode,omitempty" mapstructure:"mode,omitempty"` +const HttpProbeSchemeHTTP HttpProbeScheme = "HTTP" +const HttpProbeSchemeHTTPS HttpProbeScheme = "HTTPS" - // If set to true, the placeholders expansion will not occur in the contents of - // the file. - NoExpand *bool `json:"noExpand,omitempty" yaml:"noExpand,omitempty" mapstructure:"noExpand,omitempty"` +// An HTTP probe details. +type HttpProbe struct { + // Host name to connect to. Defaults to the workload IP. The is equivalent to a + // Host HTTP header. + Host *string `json:"host,omitempty" yaml:"host,omitempty" mapstructure:"host,omitempty"` - // The relative or absolute path to the content file. - Source *string `json:"source,omitempty" yaml:"source,omitempty" mapstructure:"source,omitempty"` + // Additional HTTP headers to send with the request + HttpHeaders []HttpProbeHttpHeadersElem `json:"httpHeaders,omitempty" yaml:"httpHeaders,omitempty" mapstructure:"httpHeaders,omitempty"` - // The file path to expose in the container. - Target string `json:"target" yaml:"target" mapstructure:"target"` + // The path to access on the HTTP server. + Path string `json:"path" yaml:"path" mapstructure:"path"` + + // The port to access on the workload. + Port int `json:"port" yaml:"port" mapstructure:"port"` + + // Scheme to use for connecting to the host (HTTP or HTTPS). Defaults to HTTP. + Scheme *HttpProbeScheme `json:"scheme,omitempty" yaml:"scheme,omitempty" mapstructure:"scheme,omitempty"` } -type ContainerProbe struct { - // HttpGet corresponds to the JSON schema field "httpGet". - HttpGet HttpProbe `json:"httpGet" yaml:"httpGet" mapstructure:"httpGet"` +// UnmarshalJSON implements json.Unmarshaler. +func (j *HttpProbe) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if v, ok := raw["path"]; !ok || v == nil { + return fmt.Errorf("field path in HttpProbe: required") + } + if v, ok := raw["port"]; !ok || v == nil { + return fmt.Errorf("field port in HttpProbe: required") + } + type Plain HttpProbe + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + if plain.Host != nil && len(*plain.Host) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "host", 1) + } + *j = HttpProbe(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Container) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if v, ok := raw["image"]; !ok || v == nil { + return fmt.Errorf("field image in Container: required") + } + type Plain Container + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + if len(plain.Image) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "image", 1) + } + *j = Container(plain) + return nil +} + +// The compute and memory resource limits. +type ResourcesLimits struct { + // The CPU limit as whole or fractional CPUs. 'm' indicates milli-CPUs. For + // example 2 or 125m. + Cpu *string `json:"cpu,omitempty" yaml:"cpu,omitempty" mapstructure:"cpu,omitempty"` + + // The memory limit in bytes with optional unit specifier. For example 125M or + // 1Gi. + Memory *string `json:"memory,omitempty" yaml:"memory,omitempty" mapstructure:"memory,omitempty"` } // The compute resources for the container. @@ -85,58 +235,68 @@ type ContainerVolumesElem struct { Target string `json:"target" yaml:"target" mapstructure:"target"` } -// An HTTP probe details. -type HttpProbe struct { - // Host name to connect to. Defaults to the workload IP. The is equivalent to a - // Host HTTP header. - Host *string `json:"host,omitempty" yaml:"host,omitempty" mapstructure:"host,omitempty"` +// The details of a file to mount in the container. One of 'source', 'content', or +// 'binaryContent' must be provided. +type ContainerFilesElem struct { + // Inline standard-base64 encoded content for the file. Does not support + // placeholder expansion. + BinaryContent *string `json:"binaryContent,omitempty" yaml:"binaryContent,omitempty" mapstructure:"binaryContent,omitempty"` - // Additional HTTP headers to send with the request - HttpHeaders []HttpProbeHttpHeadersElem `json:"httpHeaders,omitempty" yaml:"httpHeaders,omitempty" mapstructure:"httpHeaders,omitempty"` + // The inline content for the file. Only supports valid utf-8. + Content *string `json:"content,omitempty" yaml:"content,omitempty" mapstructure:"content,omitempty"` - // The path to access on the HTTP server. - Path string `json:"path" yaml:"path" mapstructure:"path"` + // The optional file access mode in octal encoding. For example 0600. + Mode *string `json:"mode,omitempty" yaml:"mode,omitempty" mapstructure:"mode,omitempty"` - // The port to access on the workload. - Port int `json:"port" yaml:"port" mapstructure:"port"` + // If set to true, the placeholders expansion will not occur in the contents of + // the file. + NoExpand *bool `json:"noExpand,omitempty" yaml:"noExpand,omitempty" mapstructure:"noExpand,omitempty"` - // Scheme to use for connecting to the host (HTTP or HTTPS). Defaults to HTTP. - Scheme *HttpProbeScheme `json:"scheme,omitempty" yaml:"scheme,omitempty" mapstructure:"scheme,omitempty"` + // The relative or absolute path to the content file. + Source *string `json:"source,omitempty" yaml:"source,omitempty" mapstructure:"source,omitempty"` + + // The file path to expose in the container. + Target string `json:"target" yaml:"target" mapstructure:"target"` } -type HttpProbeHttpHeadersElem struct { - // The HTTP header name. - Name string `json:"name" yaml:"name" mapstructure:"name"` +// The specification of a Container within the Workload. +type Container struct { + // If specified, overrides the arguments passed to the container entrypoint. + Args []string `json:"args,omitempty" yaml:"args,omitempty" mapstructure:"args,omitempty"` - // The HTTP header value. - Value string `json:"value" yaml:"value" mapstructure:"value"` -} + // If specified, overrides the entrypoint defined in the container image. + Command []string `json:"command,omitempty" yaml:"command,omitempty" mapstructure:"command,omitempty"` -type HttpProbeScheme string + // The extra files to mount into the container. + Files []ContainerFilesElem `json:"files,omitempty" yaml:"files,omitempty" mapstructure:"files,omitempty"` -const HttpProbeSchemeHTTP HttpProbeScheme = "HTTP" -const HttpProbeSchemeHTTPS HttpProbeScheme = "HTTPS" + // The container image name and tag. + Image string `json:"image" yaml:"image" mapstructure:"image"` -// The set of Resources associated with this Workload. -type Resource struct { - // An optional specialisation of the Resource type. - Class *string `json:"class,omitempty" yaml:"class,omitempty" mapstructure:"class,omitempty"` + // The liveness probe for the container. + LivenessProbe *ContainerProbe `json:"livenessProbe,omitempty" yaml:"livenessProbe,omitempty" mapstructure:"livenessProbe,omitempty"` - // An optional Resource identifier. The id may be up to 63 characters, including - // one or more labels of a-z, 0-9, '-' not starting or ending with '-' separated - // by '.'. When two resources share the same type, class, and id, they are - // considered the same resource when used across related Workloads. - Id *string `json:"id,omitempty" yaml:"id,omitempty" mapstructure:"id,omitempty"` + // The readiness probe for the container. + ReadinessProbe *ContainerProbe `json:"readinessProbe,omitempty" yaml:"readinessProbe,omitempty" mapstructure:"readinessProbe,omitempty"` - // The metadata for the Resource. - Metadata ResourceMetadata `json:"metadata,omitempty" yaml:"metadata,omitempty" mapstructure:"metadata,omitempty"` + // The compute resources for the container. + Resources *ContainerResources `json:"resources,omitempty" yaml:"resources,omitempty" mapstructure:"resources,omitempty"` - // Optional parameters used to provision the Resource in the environment. - Params ResourceParams `json:"params,omitempty" yaml:"params,omitempty" mapstructure:"params,omitempty"` + // The environment variables for the container. + Variables ContainerVariables `json:"variables,omitempty" yaml:"variables,omitempty" mapstructure:"variables,omitempty"` - // The Resource type. This should be a type supported by the Score implementations - // being used. - Type string `json:"type" yaml:"type" mapstructure:"type"` + // The volumes to mount. + Volumes []ContainerVolumesElem `json:"volumes,omitempty" yaml:"volumes,omitempty" mapstructure:"volumes,omitempty"` +} + +// The probe may be defined as either http, command execution, or both. The +// execProbe should be preferred if the Score implementation supports both types. +type ContainerProbe struct { + // Exec corresponds to the JSON schema field "exec". + Exec *ExecProbe `json:"exec,omitempty" yaml:"exec,omitempty" mapstructure:"exec,omitempty"` + + // HttpGet corresponds to the JSON schema field "httpGet". + HttpGet *HttpProbe `json:"httpGet,omitempty" yaml:"httpGet,omitempty" mapstructure:"httpGet,omitempty"` } // The metadata for the Resource. @@ -145,240 +305,128 @@ type ResourceMetadata map[string]interface{} // Optional parameters used to provision the Resource in the environment. type ResourceParams map[string]interface{} -// UnmarshalJSON implements json.Unmarshaler. -func (j *ServicePortProtocol) UnmarshalJSON(b []byte) error { - var v string - if err := json.Unmarshal(b, &v); err != nil { - return err - } - var ok bool - for _, expected := range enumValues_ServicePortProtocol { - if reflect.DeepEqual(v, expected) { - ok = true - break - } - } - if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_ServicePortProtocol, v) - } - *j = ServicePortProtocol(v) - return nil -} +// The set of Resources associated with this Workload. +type Resource struct { + // An optional specialisation of the Resource type. + Class *string `json:"class,omitempty" yaml:"class,omitempty" mapstructure:"class,omitempty"` -// UnmarshalJSON implements json.Unmarshaler. -func (j *HttpProbeHttpHeadersElem) UnmarshalJSON(b []byte) error { - var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { - return err - } - if v, ok := raw["name"]; !ok || v == nil { - return fmt.Errorf("field name in HttpProbeHttpHeadersElem: required") - } - if v, ok := raw["value"]; !ok || v == nil { - return fmt.Errorf("field value in HttpProbeHttpHeadersElem: required") - } - type Plain HttpProbeHttpHeadersElem - var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { - return err - } - if len(plain.Value) < 1 { - return fmt.Errorf("field %s length: must be >= %d", "value", 1) - } - *j = HttpProbeHttpHeadersElem(plain) - return nil -} + // An optional Resource identifier. The id may be up to 63 characters, including + // one or more labels of a-z, 0-9, '-' not starting or ending with '-' separated + // by '.'. When two resources share the same type, class, and id, they are + // considered the same resource when used across related Workloads. + Id *string `json:"id,omitempty" yaml:"id,omitempty" mapstructure:"id,omitempty"` -// UnmarshalJSON implements json.Unmarshaler. -func (j *ContainerProbe) UnmarshalJSON(b []byte) error { - var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { - return err - } - if v, ok := raw["httpGet"]; !ok || v == nil { - return fmt.Errorf("field httpGet in ContainerProbe: required") - } - type Plain ContainerProbe - var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { - return err - } - *j = ContainerProbe(plain) - return nil -} + // The metadata for the Resource. + Metadata ResourceMetadata `json:"metadata,omitempty" yaml:"metadata,omitempty" mapstructure:"metadata,omitempty"` -// UnmarshalJSON implements json.Unmarshaler. -func (j *ContainerVolumesElem) UnmarshalJSON(b []byte) error { - var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { - return err - } - if v, ok := raw["source"]; !ok || v == nil { - return fmt.Errorf("field source in ContainerVolumesElem: required") - } - if v, ok := raw["target"]; !ok || v == nil { - return fmt.Errorf("field target in ContainerVolumesElem: required") - } - type Plain ContainerVolumesElem - var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { - return err - } - *j = ContainerVolumesElem(plain) - return nil + // Optional parameters used to provision the Resource in the environment. + Params ResourceParams `json:"params,omitempty" yaml:"params,omitempty" mapstructure:"params,omitempty"` + + // The Resource type. This should be a type supported by the Score implementations + // being used. + Type string `json:"type" yaml:"type" mapstructure:"type"` } // UnmarshalJSON implements json.Unmarshaler. -func (j *ContainerFilesElem) UnmarshalJSON(b []byte) error { +func (j *Resource) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["target"]; !ok || v == nil { - return fmt.Errorf("field target in ContainerFilesElem: required") + if v, ok := raw["type"]; !ok || v == nil { + return fmt.Errorf("field type in Resource: required") } - type Plain ContainerFilesElem + type Plain Resource var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - if plain.Source != nil && len(*plain.Source) < 1 { - return fmt.Errorf("field %s length: must be >= %d", "source", 1) + if plain.Class != nil && len(*plain.Class) < 2 { + return fmt.Errorf("field %s length: must be >= %d", "class", 2) } - if len(plain.Target) < 1 { - return fmt.Errorf("field %s length: must be >= %d", "target", 1) + if plain.Class != nil && len(*plain.Class) > 63 { + return fmt.Errorf("field %s length: must be <= %d", "class", 63) } - *j = ContainerFilesElem(plain) - return nil -} - -// UnmarshalJSON implements json.Unmarshaler. -func (j *Container) UnmarshalJSON(b []byte) error { - var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { - return err + if plain.Id != nil && len(*plain.Id) < 2 { + return fmt.Errorf("field %s length: must be >= %d", "id", 2) } - if v, ok := raw["image"]; !ok || v == nil { - return fmt.Errorf("field image in Container: required") + if plain.Id != nil && len(*plain.Id) > 63 { + return fmt.Errorf("field %s length: must be <= %d", "id", 63) } - type Plain Container - var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { - return err + if len(plain.Type) < 2 { + return fmt.Errorf("field %s length: must be >= %d", "type", 2) } - if len(plain.Image) < 1 { - return fmt.Errorf("field %s length: must be >= %d", "image", 1) + if len(plain.Type) > 63 { + return fmt.Errorf("field %s length: must be <= %d", "type", 63) } - *j = Container(plain) + *j = Resource(plain) return nil } -var enumValues_HttpProbeScheme = []interface{}{ - "HTTP", - "HTTPS", -} +type ServicePortProtocol string // UnmarshalJSON implements json.Unmarshaler. -func (j *HttpProbe) UnmarshalJSON(b []byte) error { +func (j *ServicePort) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["path"]; !ok || v == nil { - return fmt.Errorf("field path in HttpProbe: required") - } if v, ok := raw["port"]; !ok || v == nil { - return fmt.Errorf("field port in HttpProbe: required") + return fmt.Errorf("field port in ServicePort: required") } - type Plain HttpProbe + type Plain ServicePort var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - if plain.Host != nil && len(*plain.Host) < 1 { - return fmt.Errorf("field %s length: must be >= %d", "host", 1) - } - *j = HttpProbe(plain) + *j = ServicePort(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. -func (j *HttpProbeScheme) UnmarshalJSON(b []byte) error { +func (j *ServicePortProtocol) UnmarshalJSON(b []byte) error { var v string if err := json.Unmarshal(b, &v); err != nil { return err } var ok bool - for _, expected := range enumValues_HttpProbeScheme { + for _, expected := range enumValues_ServicePortProtocol { if reflect.DeepEqual(v, expected) { ok = true break } } if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_HttpProbeScheme, v) + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_ServicePortProtocol, v) } - *j = HttpProbeScheme(v) + *j = ServicePortProtocol(v) return nil } // UnmarshalJSON implements json.Unmarshaler. -func (j *Resource) UnmarshalJSON(b []byte) error { +func (j *ContainerFilesElem) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["type"]; !ok || v == nil { - return fmt.Errorf("field type in Resource: required") + if v, ok := raw["target"]; !ok || v == nil { + return fmt.Errorf("field target in ContainerFilesElem: required") } - type Plain Resource + type Plain ContainerFilesElem var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - if plain.Class != nil && len(*plain.Class) < 2 { - return fmt.Errorf("field %s length: must be >= %d", "class", 2) - } - if plain.Class != nil && len(*plain.Class) > 63 { - return fmt.Errorf("field %s length: must be <= %d", "class", 63) - } - if plain.Id != nil && len(*plain.Id) < 2 { - return fmt.Errorf("field %s length: must be >= %d", "id", 2) - } - if plain.Id != nil && len(*plain.Id) > 63 { - return fmt.Errorf("field %s length: must be <= %d", "id", 63) - } - if len(plain.Type) < 2 { - return fmt.Errorf("field %s length: must be >= %d", "type", 2) + if plain.Source != nil && len(*plain.Source) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "source", 1) } - if len(plain.Type) > 63 { - return fmt.Errorf("field %s length: must be <= %d", "type", 63) + if len(plain.Target) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "target", 1) } - *j = Resource(plain) + *j = ContainerFilesElem(plain) return nil } -type ServicePortProtocol string - -var enumValues_ServicePortProtocol = []interface{}{ - "TCP", - "UDP", -} - -// The compute and memory resource limits. -type ResourcesLimits struct { - // The CPU limit as whole or fractional CPUs. 'm' indicates milli-CPUs. For - // example 2 or 125m. - Cpu *string `json:"cpu,omitempty" yaml:"cpu,omitempty" mapstructure:"cpu,omitempty"` - - // The memory limit in bytes with optional unit specifier. For example 125M or - // 1Gi. - Memory *string `json:"memory,omitempty" yaml:"memory,omitempty" mapstructure:"memory,omitempty"` -} - -const ServicePortProtocolTCP ServicePortProtocol = "TCP" -const ServicePortProtocolUDP ServicePortProtocol = "UDP" - // The network port description. type ServicePort struct { // The public service port. @@ -391,22 +439,28 @@ type ServicePort struct { TargetPort *int `json:"targetPort,omitempty" yaml:"targetPort,omitempty" mapstructure:"targetPort,omitempty"` } -// UnmarshalJSON implements json.Unmarshaler. -func (j *ServicePort) UnmarshalJSON(b []byte) error { - var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { - return err - } - if v, ok := raw["port"]; !ok || v == nil { - return fmt.Errorf("field port in ServicePort: required") - } - type Plain ServicePort - var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { - return err - } - *j = ServicePort(plain) - return nil +const ServicePortProtocolUDP ServicePortProtocol = "UDP" + +// Score workload specification +type Workload struct { + // The declared Score Specification version. + ApiVersion string `json:"apiVersion" yaml:"apiVersion" mapstructure:"apiVersion"` + + // The set of named containers in the Workload. The container name must be a valid + // RFC1123 Label Name of up to 63 characters, including a-z, 0-9, '-' but may not + // start or end with '-'. + Containers WorkloadContainers `json:"containers" yaml:"containers" mapstructure:"containers"` + + // The metadata description of the Workload. + Metadata WorkloadMetadata `json:"metadata" yaml:"metadata" mapstructure:"metadata"` + + // The Resource dependencies needed by the Workload. The resource name must be a + // valid RFC1123 Label Name of up to 63 characters, including a-z, 0-9, '-' but + // may not start or end with '-'. + Resources WorkloadResources `json:"resources,omitempty" yaml:"resources,omitempty" mapstructure:"resources,omitempty"` + + // The service that the workload provides. + Service *WorkloadService `json:"service,omitempty" yaml:"service,omitempty" mapstructure:"service,omitempty"` } // The set of named containers in the Workload. The container name must be a valid @@ -422,11 +476,6 @@ type WorkloadMetadata map[string]interface{} // not start or end with '-'. type WorkloadResources map[string]Resource -// The set of named network ports published by the service. The service name must -// be a valid RFC1123 Label Name of up to 63 characters, including a-z, 0-9, '-' -// but may not start or end with '-'. -type WorkloadServicePorts map[string]ServicePort - // The service that the workload provides. type WorkloadService struct { // The set of named network ports published by the service. The service name must @@ -435,48 +484,16 @@ type WorkloadService struct { Ports WorkloadServicePorts `json:"ports,omitempty" yaml:"ports,omitempty" mapstructure:"ports,omitempty"` } -// Score workload specification -type Workload struct { - // The declared Score Specification version. - ApiVersion string `json:"apiVersion" yaml:"apiVersion" mapstructure:"apiVersion"` - - // The set of named containers in the Workload. The container name must be a valid - // RFC1123 Label Name of up to 63 characters, including a-z, 0-9, '-' but may not - // start or end with '-'. - Containers WorkloadContainers `json:"containers" yaml:"containers" mapstructure:"containers"` - - // The metadata description of the Workload. - Metadata WorkloadMetadata `json:"metadata" yaml:"metadata" mapstructure:"metadata"` - - // The Resource dependencies needed by the Workload. The resource name must be a - // valid RFC1123 Label Name of up to 63 characters, including a-z, 0-9, '-' but - // may not start or end with '-'. - Resources WorkloadResources `json:"resources,omitempty" yaml:"resources,omitempty" mapstructure:"resources,omitempty"` +// The set of named network ports published by the service. The service name must +// be a valid RFC1123 Label Name of up to 63 characters, including a-z, 0-9, '-' +// but may not start or end with '-'. +type WorkloadServicePorts map[string]ServicePort - // The service that the workload provides. - Service *WorkloadService `json:"service,omitempty" yaml:"service,omitempty" mapstructure:"service,omitempty"` +var enumValues_HttpProbeScheme = []interface{}{ + "HTTP", + "HTTPS", } - -// UnmarshalJSON implements json.Unmarshaler. -func (j *Workload) UnmarshalJSON(b []byte) error { - var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { - return err - } - if v, ok := raw["apiVersion"]; !ok || v == nil { - return fmt.Errorf("field apiVersion in Workload: required") - } - if v, ok := raw["containers"]; !ok || v == nil { - return fmt.Errorf("field containers in Workload: required") - } - if v, ok := raw["metadata"]; !ok || v == nil { - return fmt.Errorf("field metadata in Workload: required") - } - type Plain Workload - var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { - return err - } - *j = Workload(plain) - return nil +var enumValues_ServicePortProtocol = []interface{}{ + "TCP", + "UDP", }