From 713e8456a14146fe3e0ac2d3cb6c00591a6ba317 Mon Sep 17 00:00:00 2001 From: Ben Meier Date: Thu, 25 Jan 2024 15:59:12 +0000 Subject: [PATCH] feat!: replace manual types with generated types and cut a v1 module Signed-off-by: Ben Meier --- README.md | 38 ++- go.mod | 4 +- loader/loader.go | 5 +- loader/loader_test.go | 95 ++++--- schema/files/README.md | 4 +- schema/files/score-v1b1.json | 523 ++++++++++++++++++----------------- schema/schema_test.go | 62 +---- types/containers.go | 75 ----- types/resources.go | 24 -- types/service.go | 23 -- types/types.gen.go | 400 +++++++++++++++++++++++++++ types/types.go | 3 + types/workload.go | 84 ------ 13 files changed, 779 insertions(+), 561 deletions(-) delete mode 100644 types/containers.go delete mode 100644 types/resources.go delete mode 100644 types/service.go create mode 100644 types/types.gen.go create mode 100644 types/types.go delete mode 100644 types/workload.go diff --git a/README.md b/README.md index af52534..c8dcd22 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,15 @@ # score-go -Reference library for the parsing and loading SCORE files + +Reference library for the parsing and loading SCORE files in Go. + +This can be added to your project via: + +```sh +$ go get -u github.com/score-spec/score-go/v1 +``` + +**NOTE**: if you project is still using the hand-written types, you will need to stay on `github.com/score-spec/score-go` +and any important fixes to the schema may be back-ported to that branch. ## Parsing SCORE files @@ -10,8 +20,8 @@ import ( "io" "os" - "github.com/score-spec/score-go/loader" - score "github.com/score-spec/score-go/types" + "github.com/score-spec/score-go/v1/loader" + score "github.com/score-spec/score-go/v1/types" ) func main() { @@ -40,3 +50,25 @@ func main() { } ``` + +## Upgrading the schema version + +When the Score JSON schema is updated in https://github.com/score-spec/schema, this repo should be updated to match. + +First update the subtree: + +``` +git subtree pull --prefix schema/files git@github.com:score-spec/schema.git main --squash +``` + +Then regenerate the defined types: + +``` +go generate -v ./... +``` + +And ensure the tests still pass: + +``` +go test -v ./... +``` diff --git a/go.mod b/go.mod index fa6069f..d2b6dc1 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ -module github.com/score-spec/score-go +module github.com/score-spec/score-go/v1 -go 1.19 +go 1.21 require ( github.com/mitchellh/mapstructure v1.5.0 diff --git a/loader/loader.go b/loader/loader.go index 4392806..9389466 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -12,8 +12,9 @@ import ( "io" "github.com/mitchellh/mapstructure" - "github.com/score-spec/score-go/types" "gopkg.in/yaml.v3" + + "github.com/score-spec/score-go/v1/types" ) // ParseYAML parses YAML into the target mapping structure. @@ -22,7 +23,7 @@ func ParseYAML(dest *map[string]interface{}, r io.Reader) error { } // MapSpec converts the source mapping structure into the target WorkloadSpec. -func MapSpec(dest *types.WorkloadSpec, src map[string]interface{}) error { +func MapSpec(dest *types.Workload, src map[string]interface{}) error { mapper, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ Result: dest, TagName: "json", diff --git a/loader/loader_test.go b/loader/loader_test.go index 2ac0f0b..8a2f167 100644 --- a/loader/loader_test.go +++ b/loader/loader_test.go @@ -13,15 +13,32 @@ import ( "io" "testing" - "github.com/score-spec/score-go/types" "github.com/stretchr/testify/assert" + + "github.com/score-spec/score-go/v1/types" ) +func stringRef(input string) *string { + return &input +} + +func intRef(input int) *int { + return &input +} + +func boolRef(input bool) *bool { + return &input +} + +func schemeRef(input types.HttpProbeScheme) *types.HttpProbeScheme { + return &input +} + func TestDecodeYaml(t *testing.T) { var tests = []struct { Name string Source io.Reader - Output types.WorkloadSpec + Output types.Workload Error error }{ { @@ -113,84 +130,82 @@ resources: } } `)), - Output: types.WorkloadSpec{ + Output: types.Workload{ ApiVersion: "score.dev/v1b1", - Metadata: types.WorkloadMeta{ + Metadata: types.WorkloadMetadata{ Name: "hello-world", }, - Service: types.ServiceSpec{ - Ports: types.ServicePortsSpecs{ - "www": types.ServicePortSpec{ + Service: &types.WorkloadService{ + Ports: types.WorkloadServicePorts{ + "www": types.ServicePort{ Port: 80, - Protocol: "", - TargetPort: 8080, + TargetPort: intRef(8080), }, }, }, - Containers: types.ContainersSpecs{ - "hello": types.ContainerSpec{ + Containers: types.WorkloadContainers{ + "hello": types.Container{ Image: "busybox", Command: []string{"/bin/echo"}, Args: []string{"Hello $(FRIEND)"}, Variables: map[string]string{ "FRIEND": "World!", }, - Files: []types.FileMountSpec{ + Files: []types.ContainerFilesElem{ { Target: "/etc/hello-world/config.yaml", - Mode: "666", - Source: "", + Mode: stringRef("666"), Content: "---\n${resources.env.APP_CONFIG}\n", - NoExpand: true, + NoExpand: boolRef(true), }, }, - Volumes: []types.VolumeMountSpec{ + Volumes: []types.ContainerVolumesElem{ { Source: "${resources.data}", - Path: "sub/path", + Path: stringRef("sub/path"), Target: "/mnt/data", - ReadOnly: true, + ReadOnly: boolRef(true), }, }, - Resources: types.ContainerResourcesRequirementsSpec{ - Limits: map[string]interface{}{ - "memory": "128Mi", - "cpu": "500m", + Resources: &types.ContainerResources{ + Limits: &types.ResourcesLimits{ + Memory: stringRef("128Mi"), + Cpu: stringRef("500m"), }, - Requests: map[string]interface{}{ - "memory": "64Mi", - "cpu": "250m", + Requests: &types.ResourcesLimits{ + Memory: stringRef("64Mi"), + Cpu: stringRef("250m"), }, }, - LivenessProbe: types.ContainerProbeSpec{ - HTTPGet: types.HTTPGetActionSpec{ + LivenessProbe: &types.ContainerProbe{ + HttpGet: &types.HttpProbe{ Path: "/alive", - Port: 8080, + Port: intRef(8080), }, }, - ReadinessProbe: types.ContainerProbeSpec{ - HTTPGet: types.HTTPGetActionSpec{ - Host: "1.1.1.1", - Scheme: "HTTPS", + ReadinessProbe: &types.ContainerProbe{ + HttpGet: &types.HttpProbe{ + Host: stringRef("1.1.1.1"), + Scheme: schemeRef(types.HttpProbeSchemeHTTPS), Path: "/ready", - Port: 8080, - HTTPHeaders: []types.HTTPHeaderSpec{ - {Name: "Custom-Header", Value: "Awesome"}, + Port: intRef(8080), + HttpHeaders: []types.HttpProbeHttpHeadersElem{ + {Name: stringRef("Custom-Header"), Value: stringRef("Awesome")}, }, }, }, }, }, - Resources: map[string]types.ResourceSpec{ + Resources: types.WorkloadResources{ "env": { Type: "environment", }, - "dns": {Type: "dns", Class: "sensitive"}, + "dns": {Type: "dns", Class: stringRef("sensitive")}, "data": {Type: "volume"}, "db": { Type: "postgres", - Class: "large", - Metadata: types.ResourceMeta{ + Class: stringRef("large"), + Metadata: &types.ResourceMetadata{ Annotations: map[string]string{ "my.org/version": "0.1", }, @@ -212,7 +227,7 @@ resources: for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { var srcMap map[string]interface{} - var spec types.WorkloadSpec + var spec types.Workload var err = ParseYAML(&srcMap, tt.Source) if err == nil { diff --git a/schema/files/README.md b/schema/files/README.md index e5cc499..a39a2d2 100644 --- a/schema/files/README.md +++ b/schema/files/README.md @@ -1,7 +1,7 @@ # JSON schemas for Score files -| version | file | -| --- | --- | +| version | file | +|----------|-----------------| | v1-beta1 | score-v1b1.json | ## Embed schemas into project diff --git a/schema/files/score-v1b1.json b/schema/files/score-v1b1.json index 219c687..bbda8e9 100644 --- a/schema/files/score-v1b1.json +++ b/schema/files/score-v1b1.json @@ -37,27 +37,9 @@ "ports": { "description": "List of network ports published by the service.", "type": "object", - "additionalProperties": true, "minProperties": 1, - "patternProperties": { - "^*": { - "description": "The network port description.", - "type": "object", - "required": [ - "targetPort" - ], - "additionalProperties": false, - "properties": { - "port": { - "description": "The public service port.", - "type": "number" - }, - "targetPort": { - "description": "The internal service port.", - "type": "number" - } - } - } + "additionalProperties": { + "$ref": "#/$defs/servicePort" } } } @@ -65,277 +47,302 @@ "containers": { "description": "The declared Score Specification version.", "type": "object", - "additionalProperties": true, "minProperties": 1, - "patternProperties": { - "^*": { - "description": "The container name.", + "additionalProperties": { + "$ref": "#/$defs/container" + } + }, + "resources": { + "description": "The dependencies needed by the Workload.", + "type": "object", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/$defs/resource" + } + } + }, + "$defs": { + "servicePort": { + "description": "The network port description.", + "type": "object", + "required": [ + "port" + ], + "additionalProperties": false, + "properties": { + "port": { + "description": "The public service port.", + "type": "integer" + }, + "protocol": { + "description": "The transport level protocol. Defaults to TCP.", + "type": "string" + }, + "targetPort": { + "description": "The internal service port. This will default to 'port' if not provided.", + "type": "integer" + } + } + }, + "resource": { + "description": "The resource name.", + "type": "object", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "type": { + "description": "The resource in the target environment.", + "type": "string" + }, + "class": { + "description": "A specialisation of the resource type.", + "type": "string", + "pattern": "^[a-z0-9](?:-?[a-z0-9]+)+$" + }, + "metadata": { + "description": "The metadata for the resource.", "type": "object", - "required": [ - "image" - ], - "additionalProperties": false, + "minProperties": 1, + "additionalProperties": true, "properties": { - "image": { - "description": "The image name and tag.", - "type": "string" - }, - "command": { - "description": "If specified, overrides container entry point.", - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - }, - "args": { - "description": "If specified, overrides container entry point arguments.", - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - }, - "variables": { - "description": "The environment variables for the container.", + "annotations": { + "description": "Annotations that apply to the property.", "type": "object", "minProperties": 1, "additionalProperties": { "type": "string" } - }, - "files": { - "description": "The extra files to mount.", - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "required": [ - "target" - ], - "properties": { - "target": { - "description": "The file path and name.", - "type": "string" - }, - "mode": { - "description": "The file access mode.", + } + } + }, + "params": { + "description": "The parameters used to validate or provision the resource in the environment.", + "type": "object" + } + } + }, + "resourcesLimits": { + "description": "The compute resources limits.", + "type": "object", + "minProperties": 1, + "additionalProperties": false, + "properties": { + "memory": { + "description": "The memory limit.", + "type": "string" + }, + "cpu": { + "description": "The CPU limit.", + "type": "string" + } + } + }, + "container": { + "description": "The container name.", + "type": "object", + "required": [ + "image" + ], + "additionalProperties": false, + "properties": { + "image": { + "description": "The image name and tag.", + "type": "string" + }, + "command": { + "description": "If specified, overrides container entry point.", + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "args": { + "description": "If specified, overrides container entry point arguments.", + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "variables": { + "description": "The environment variables for the container.", + "type": "object", + "minProperties": 1, + "additionalProperties": { + "type": "string" + } + }, + "files": { + "description": "The extra files to mount.", + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "target" + ], + "properties": { + "target": { + "description": "The file path and name.", + "type": "string" + }, + "mode": { + "description": "The file access mode.", + "type": "string" + }, + "source": { + "description": "The relative or absolute path to the content file.", + "type": "string", + "minLength": 1 + }, + "content": { + "description": "The inline content for the file.", + "anyOf": [ + { "type": "string" }, - "source": { - "description": "The relative or absolute path to the content file.", - "type": "string", - "minLength": 1 - }, - "content": { - "description": "The inline content for the file.", - "anyOf": [{ - "type": "string" - }, { - "deprecated": true, - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - }] - }, - "noExpand": { - "description": "If set to true, the placeholders expansion will not occur in the contents of the file.", - "type": "boolean" + { + "deprecated": true, + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } } - }, - "oneOf": [{ - "required": ["content"] - }, { - "required": ["source"] - }] + ] + }, + "noExpand": { + "description": "If set to true, the placeholders expansion will not occur in the contents of the file.", + "type": "boolean" } }, - "volumes": { - "description": "The volumes to mount.", - "type": "array", - "minItems": 1, - "items": { - "type": "object", + "oneOf": [ + { "required": [ - "source", - "target" - ], - "properties": { - "source": { - "description": "The external volume reference.", - "type": "string" - }, - "path": { - "description": "An optional sub path in the volume.", - "type": "string" - }, - "target": { - "description": "The target mount on the container.", - "type": "string" - }, - "read_only": { - "description": "Indicates if the volume should be mounted in a read-only mode.", - "type": "boolean" - } - } + "content" + ] + }, + { + "required": [ + "source" + ] } - }, - "resources": { - "description": "The compute resources for the container.", - "type": "object", - "minProperties": 1, - "additionalProperties": false, - "properties": { - "limits": { - "description": "The maximum allowed resources for the container.", - "$ref": "#/properties/containers/definitions/resourcesLimits" - }, - "requests": { - "description": "The minimal resources required for the container.", - "$ref": "#/properties/containers/definitions/resourcesLimits" - } + ] + } + }, + "volumes": { + "description": "The volumes to mount.", + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "source", + "target" + ], + "properties": { + "source": { + "description": "The external volume reference.", + "type": "string" + }, + "path": { + "description": "An optional sub path in the volume.", + "type": "string" + }, + "target": { + "description": "The target mount on the container.", + "type": "string" + }, + "read_only": { + "description": "Indicates if the volume should be mounted in a read-only mode.", + "type": "boolean" } - }, - "livenessProbe": { - "description": "The liveness probe for the container.", - "$ref": "#/properties/containers/definitions/containerProbe" - }, - "readinessProbe": { - "description": "The readiness probe for the container.", - "$ref": "#/properties/containers/definitions/containerProbe" } } - } - }, - "definitions": { - "resourcesLimits": { - "description": "The compute resources limits.", + }, + "resources": { + "description": "The compute resources for the container.", "type": "object", "minProperties": 1, "additionalProperties": false, "properties": { - "memory": { - "description": "The memory limit.", - "type": "string" + "limits": { + "description": "The maximum allowed resources for the container.", + "$ref": "#/$defs/resourcesLimits" }, - "cpu": { - "description": "The CPU limit.", - "type": "string" + "requests": { + "description": "The minimal resources required for the container.", + "$ref": "#/$defs/resourcesLimits" } } }, - "containerProbe": { - "type": "object", - "minProperties": 1, - "additionalProperties": false, - "properties": { - "httpGet": { - "$ref": "#/properties/containers/definitions/httpProbe" - } - } + "livenessProbe": { + "description": "The liveness probe for the container.", + "$ref": "#/$defs/containerProbe" }, - "httpProbe": { - "description": "An HTTP probe details.", - "type": "object", - "additionalProperties": false, - "required": [ - "path" - ], - "properties": { - "host": { - "description": "Host name to connect to. Defaults to the container IP.", - "type": "string" - }, - "scheme": { - "description": "Scheme to use for connecting to the host (HTTP or HTTPS). Defaults to HTTP.", - "type": "string", - "enum": [ - "HTTP", - "HTTPS" - ] - }, - "path": { - "description": "The path of the HTTP probe endpoint.", - "type": "string" - }, - "port": { - "description": "The path of the HTTP probe endpoint.", - "type": "number" - }, - "httpHeaders": { - "description": "Additional HTTP headers to send with the request", - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "description": "The HTTP header name.", - "type": "string" - }, - "value": { - "description": "The HTTP header value.", - "type": "string" - } - } - } - } - } + "readinessProbe": { + "description": "The readiness probe for the container.", + "$ref": "#/$defs/containerProbe" } } }, - "resources": { - "description": "The dependencies needed by the Workload.", + "containerProbe": { "type": "object", "minProperties": 1, - "additionalProperties": true, - "patternProperties": { - "^*": { - "description": "The resource name.", - "type": "object", - "additionalProperties": false, - "required": [ - "type" - ], - "properties": { - "type": { - "description": "The resource in the target environment.", - "type": "string" - }, - "class": { - "description": "A specialisation of the resource type.", - "type": "string", - "pattern": "^[a-z0-9](?:-?[a-z0-9]+)+$" - }, - "metadata": { - "description": "The metadata for the resource.", - "type": "object", - "minProperties": 1, - "additionalProperties": true, - "properties": { - "annotations": { - "description": "Annotations that apply to the property.", - "type": "object", - "minProperties": 1, - "additionalProperties": { - "type": "string" - } - } - } - }, + "additionalProperties": false, + "properties": { + "httpGet": { + "$ref": "#/$defs/httpProbe" + } + } + }, + "httpProbe": { + "description": "An HTTP probe details.", + "type": "object", + "additionalProperties": false, + "required": [ + "path" + ], + "properties": { + "host": { + "description": "Host name to connect to. Defaults to the container IP.", + "type": "string" + }, + "scheme": { + "description": "Scheme to use for connecting to the host (HTTP or HTTPS). Defaults to HTTP.", + "type": "string", + "enum": [ + "HTTP", + "HTTPS" + ] + }, + "path": { + "description": "The path of the HTTP probe endpoint.", + "type": "string" + }, + "port": { + "description": "The path of the HTTP probe endpoint.", + "type": "integer" + }, + "httpHeaders": { + "description": "Additional HTTP headers to send with the request", + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, "properties": { - "description": "DEPRECATED: The properties that can be referenced in other places in the Score Specification file.", - "type": [ - "object", - "null" - ] - }, - "params": { - "description": "The parameters used to validate or provision the resource in the environment.", - "type": "object" + "name": { + "description": "The HTTP header name.", + "type": "string" + }, + "value": { + "description": "The HTTP header value.", + "type": "string" + } } } } diff --git a/schema/schema_test.go b/schema/schema_test.go index 576546a..862cc5e 100644 --- a/schema/schema_test.go +++ b/schema/schema_test.go @@ -92,7 +92,7 @@ resources: var obj map[string]interface{} var yamlReader = bytes.NewReader(data) - yaml.NewDecoder(yamlReader).Decode(&obj) + _ = yaml.NewDecoder(yamlReader).Decode(&obj) return obj } @@ -227,7 +227,7 @@ func TestSchema(t *testing.T) { Message: "/service/ports/www", }, { - Name: "service.ports.*.targetPort is missing", + Name: "service.ports.*.port is missing", Src: func() map[string]interface{} { src := newTestDocument() src["service"] = map[string]interface{}{ @@ -240,50 +240,51 @@ func TestSchema(t *testing.T) { Message: "/service/ports/www", }, { - Name: "service.ports.*.targetPort is not a number", + Name: "service.ports.*.port is not a number", Src: func() map[string]interface{} { src := newTestDocument() src["service"] = map[string]interface{}{ "ports": map[string]interface{}{ "www": map[string]interface{}{ - "targetPort": false, + "port": false, + "targetPort": 8080, }, }, } return src }(), - Message: "/service/ports/www/targetPort", + Message: "/service/ports/www/port", }, { - Name: "service.ports.*.port is optional", + Name: "service.ports.*.targetPort is not a number", Src: func() map[string]interface{} { src := newTestDocument() src["service"] = map[string]interface{}{ "ports": map[string]interface{}{ "www": map[string]interface{}{ - "targetPort": 8080, + "port": 80, + "targetPort": false, }, }, } return src }(), - Message: "", + Message: "/service/ports/www/targetPort", }, { - Name: "service.ports.*.port is not a number", + Name: "service.ports.*.targetPort is optional", Src: func() map[string]interface{} { src := newTestDocument() src["service"] = map[string]interface{}{ "ports": map[string]interface{}{ "www": map[string]interface{}{ - "port": false, - "targetPort": 8080, + "port": 8080, }, }, } return src }(), - Message: "/service/ports/www/port", + Message: "", }, { Name: "service with multiple ports", @@ -296,7 +297,7 @@ func TestSchema(t *testing.T) { "targetPort": 8080, }, "admin": map[string]interface{}{ - "targetPort": 8090, + "port": 90, }, }, } @@ -1532,41 +1533,6 @@ func TestSchema(t *testing.T) { Message: "/resources/db/metadata/annotations/one", }, - // resources.*.properties - // - { - Name: "resources.*.properties is not set", - Src: func() map[string]interface{} { - src := newTestDocument() - var db = src["resources"].(map[string]interface{})["db"].(map[string]interface{}) - db["properties"] = nil - return src - }(), - Message: "", - }, - { - Name: "resources.*.properties is empty", - Src: func() map[string]interface{} { - src := newTestDocument() - var db = src["resources"].(map[string]interface{})["db"].(map[string]interface{}) - db["properties"] = map[string]interface{}{} - return src - }(), - Message: "", - }, - { - Name: "resources.*.properties is ignored", - Src: func() map[string]interface{} { - src := newTestDocument() - var db = src["resources"].(map[string]interface{})["db"].(map[string]interface{}) - db["properties"] = map[string]interface{}{ - "key": "value", - } - return src - }(), - Message: "", - }, - // resources.*.params // { diff --git a/types/containers.go b/types/containers.go deleted file mode 100644 index 39e364c..0000000 --- a/types/containers.go +++ /dev/null @@ -1,75 +0,0 @@ -/* -Apache Score -Copyright 2020 The Apache Software Foundation - -This product includes software developed at -The Apache Software Foundation (http://www.apache.org/). -*/ -package types - -// ContainersSpecs is a map of workload containers specifications. -type ContainersSpecs map[string]ContainerSpec - -// ContainerSpec is a workload container specification. -type ContainerSpec struct { - Image string `json:"image"` - Command []string `json:"command"` - Args []string `json:"args"` - Variables map[string]string `json:"variables"` - Files []FileMountSpec `json:"files"` - Volumes []VolumeMountSpec `json:"volumes"` - Resources ContainerResourcesRequirementsSpec `json:"resources"` - LivenessProbe ContainerProbeSpec `json:"livenessProbe"` - ReadinessProbe ContainerProbeSpec `json:"readinessProbe"` -} - -// ContainerResourcesRequirementsSpec is a container resources requirements. -type ContainerResourcesRequirementsSpec struct { - Limits map[string]interface{} `json:"limits"` - Requests map[string]interface{} `json:"requests"` -} - -// FileMountSpec is a container's file mount specification. -type FileMountSpec struct { - // The mounted file path and name. - Target string `json:"target"` - // The mounted file access mode. - Mode string `json:"mode"` - // File content, if scpecified, could be a single string or an array of strings (parsed as []interface{}, DEPRECATED). - // This property can't be used if 'source' property is used. - Content interface{} `json:"content"` - // If specified, file content should be read from the local file, referenced by the source property. - // The file path is always relative to the source Score file. - // This property can't be used if 'content' property is used. - Source string `json:"source"` - // If set to true, the placeholders expansion will not occur in the contents of the file. - NoExpand bool `json:"noExpand"` -} - -// VolumeMountSpec is a container volume mount point specification. -type VolumeMountSpec struct { - Source string `json:"source"` - Path string `json:"path"` - Target string `json:"target"` - ReadOnly bool `json:"read_only"` -} - -// ContainerProbeSpec is a container probe specification. -type ContainerProbeSpec struct { - HTTPGet HTTPGetActionSpec `json:"httpGet"` -} - -// HTTPGetActionSpec is an HTTP GET Action specification. -type HTTPGetActionSpec struct { - Scheme string `json:"scheme"` - Host string `json:"host"` - Port int `json:"port"` - Path string `json:"path"` - HTTPHeaders []HTTPHeaderSpec `json:"httpHeaders"` -} - -// HTTPHeaderSpec is an HTTP Header specification. -type HTTPHeaderSpec struct { - Name string `json:"name"` - Value string `json:"value"` -} diff --git a/types/resources.go b/types/resources.go deleted file mode 100644 index 3e1ca22..0000000 --- a/types/resources.go +++ /dev/null @@ -1,24 +0,0 @@ -/* -Apache Score -Copyright 2020 The Apache Software Foundation - -This product includes software developed at -The Apache Software Foundation (http://www.apache.org/). -*/ -package types - -// ResourcesSpecs is a map of workload resources specifications. -type ResourcesSpecs map[string]ResourceSpec - -// ResourceSpec is a resource specification. -type ResourceSpec struct { - Type string `json:"type"` - Class string `json:"class,omitempty"` - Metadata ResourceMeta `json:"metadata,omitempty"` - Params map[string]interface{} `json:"params,omitempty"` -} - -// ResourceMeta is an additional resource metadata. -type ResourceMeta struct { - Annotations map[string]string `json:"annotations,omitempty"` -} diff --git a/types/service.go b/types/service.go deleted file mode 100644 index 26c5f50..0000000 --- a/types/service.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -Apache Score -Copyright 2020 The Apache Software Foundation - -This product includes software developed at -The Apache Software Foundation (http://www.apache.org/). -*/ -package types - -// ServiceSpec is a workload service specification. -type ServiceSpec struct { - Ports ServicePortsSpecs `json:"ports"` -} - -// ServicePortsSpecs is a map of named service ports specifications. -type ServicePortsSpecs map[string]ServicePortSpec - -// ServicePortSpec is a service port specification. -type ServicePortSpec struct { - Port int `json:"port"` - TargetPort int `json:"targetPort"` - Protocol string `json:"protocol"` -} diff --git a/types/types.gen.go b/types/types.gen.go new file mode 100644 index 0000000..26df470 --- /dev/null +++ b/types/types.gen.go @@ -0,0 +1,400 @@ +// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. + +package types + +import "encoding/json" +import "fmt" +import "reflect" + +// The container name. +type Container struct { + // If specified, overrides container entry point arguments. + Args []string `json:"args,omitempty" yaml:"args,omitempty" mapstructure:"args,omitempty"` + + // If specified, overrides container entry point. + Command []string `json:"command,omitempty" yaml:"command,omitempty" mapstructure:"command,omitempty"` + + // The extra files to mount. + Files []ContainerFilesElem `json:"files,omitempty" yaml:"files,omitempty" mapstructure:"files,omitempty"` + + // The image name and tag. + Image string `json:"image" yaml:"image" mapstructure:"image"` + + // The liveness probe for the container. + LivenessProbe *ContainerProbe `json:"livenessProbe,omitempty" yaml:"livenessProbe,omitempty" mapstructure:"livenessProbe,omitempty"` + + // The readiness probe for the container. + ReadinessProbe *ContainerProbe `json:"readinessProbe,omitempty" yaml:"readinessProbe,omitempty" mapstructure:"readinessProbe,omitempty"` + + // The compute resources for the container. + Resources *ContainerResources `json:"resources,omitempty" yaml:"resources,omitempty" mapstructure:"resources,omitempty"` + + // The environment variables for the container. + Variables ContainerVariables `json:"variables,omitempty" yaml:"variables,omitempty" mapstructure:"variables,omitempty"` + + // The volumes to mount. + Volumes []ContainerVolumesElem `json:"volumes,omitempty" yaml:"volumes,omitempty" mapstructure:"volumes,omitempty"` +} + +type ContainerFilesElem struct { + // The inline content for the file. + Content interface{} `json:"content,omitempty" yaml:"content,omitempty" mapstructure:"content,omitempty"` + + // The file access mode. + Mode *string `json:"mode,omitempty" yaml:"mode,omitempty" mapstructure:"mode,omitempty"` + + // 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"` + + // The relative or absolute path to the content file. + Source *string `json:"source,omitempty" yaml:"source,omitempty" mapstructure:"source,omitempty"` + + // The file path and name. + Target string `json:"target" yaml:"target" mapstructure:"target"` +} + +type ContainerProbe struct { + // HttpGet corresponds to the JSON schema field "httpGet". + HttpGet *HttpProbe `json:"httpGet,omitempty" yaml:"httpGet,omitempty" mapstructure:"httpGet,omitempty"` +} + +// The compute resources for the container. +type ContainerResources struct { + // The maximum allowed resources for the container. + Limits *ResourcesLimits `json:"limits,omitempty" yaml:"limits,omitempty" mapstructure:"limits,omitempty"` + + // The minimal resources required for the container. + Requests *ResourcesLimits `json:"requests,omitempty" yaml:"requests,omitempty" mapstructure:"requests,omitempty"` +} + +// The environment variables for the container. +type ContainerVariables map[string]string + +type ContainerVolumesElem struct { + // An optional sub path in the volume. + Path *string `json:"path,omitempty" yaml:"path,omitempty" mapstructure:"path,omitempty"` + + // Indicates if the volume should be mounted in a read-only mode. + ReadOnly *bool `json:"read_only,omitempty" yaml:"read_only,omitempty" mapstructure:"read_only,omitempty"` + + // The external volume reference. + Source string `json:"source" yaml:"source" mapstructure:"source"` + + // The target mount on the container. + Target string `json:"target" yaml:"target" mapstructure:"target"` +} + +// An HTTP probe details. +type HttpProbe struct { + // Host name to connect to. Defaults to the container IP. + Host *string `json:"host,omitempty" yaml:"host,omitempty" mapstructure:"host,omitempty"` + + // Additional HTTP headers to send with the request + HttpHeaders []HttpProbeHttpHeadersElem `json:"httpHeaders,omitempty" yaml:"httpHeaders,omitempty" mapstructure:"httpHeaders,omitempty"` + + // The path of the HTTP probe endpoint. + Path string `json:"path" yaml:"path" mapstructure:"path"` + + // The path of the HTTP probe endpoint. + Port *int `json:"port,omitempty" yaml:"port,omitempty" mapstructure:"port,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"` +} + +type HttpProbeHttpHeadersElem struct { + // The HTTP header name. + Name *string `json:"name,omitempty" yaml:"name,omitempty" mapstructure:"name,omitempty"` + + // The HTTP header value. + Value *string `json:"value,omitempty" yaml:"value,omitempty" mapstructure:"value,omitempty"` +} + +type HttpProbeScheme string + +const HttpProbeSchemeHTTP HttpProbeScheme = "HTTP" +const HttpProbeSchemeHTTPS HttpProbeScheme = "HTTPS" + +// The resource name. +type Resource struct { + // A specialisation of the resource type. + Class *string `json:"class,omitempty" yaml:"class,omitempty" mapstructure:"class,omitempty"` + + // The metadata for the resource. + Metadata *ResourceMetadata `json:"metadata,omitempty" yaml:"metadata,omitempty" mapstructure:"metadata,omitempty"` + + // The parameters used to validate or provision the resource in the environment. + Params ResourceParams `json:"params,omitempty" yaml:"params,omitempty" mapstructure:"params,omitempty"` + + // The resource in the target environment. + Type string `json:"type" yaml:"type" mapstructure:"type"` +} + +// The metadata for the resource. +type ResourceMetadata struct { + // Annotations that apply to the property. + Annotations ResourceMetadataAnnotations `json:"annotations,omitempty" yaml:"annotations,omitempty" mapstructure:"annotations,omitempty"` +} + +// Annotations that apply to the property. +type ResourceMetadataAnnotations map[string]string + +// The parameters used to validate or provision the resource in the environment. +type ResourceParams map[string]interface{} + +// The compute resources limits. +type ResourcesLimits struct { + // The CPU limit. + Cpu *string `json:"cpu,omitempty" yaml:"cpu,omitempty" mapstructure:"cpu,omitempty"` + + // The memory limit. + Memory *string `json:"memory,omitempty" yaml:"memory,omitempty" mapstructure:"memory,omitempty"` +} + +// The network port description. +type ServicePort struct { + // The public service port. + Port int `json:"port" yaml:"port" mapstructure:"port"` + + // The transport level protocol. Defaults to TCP. + Protocol *string `json:"protocol,omitempty" yaml:"protocol,omitempty" mapstructure:"protocol,omitempty"` + + // The internal service port. This will default to 'port' if not provided. + TargetPort *int `json:"targetPort,omitempty" yaml:"targetPort,omitempty" mapstructure:"targetPort,omitempty"` +} + +// Score workload specification +type Workload struct { + // The declared Score Specification version. + ApiVersion string `json:"apiVersion" yaml:"apiVersion" mapstructure:"apiVersion"` + + // The declared Score Specification version. + Containers WorkloadContainers `json:"containers" yaml:"containers" mapstructure:"containers"` + + // The metadata description of the Workload. + Metadata WorkloadMetadata `json:"metadata" yaml:"metadata" mapstructure:"metadata"` + + // The dependencies needed by the Workload. + 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 declared Score Specification version. +type WorkloadContainers map[string]Container + +// The metadata description of the Workload. +type WorkloadMetadata struct { + // A string that can describe the Workload. + Name string `json:"name" yaml:"name" mapstructure:"name"` +} + +// 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 plain.Args != nil && len(plain.Args) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "args", 1) + } + if plain.Command != nil && len(plain.Command) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "command", 1) + } + if plain.Files != nil && len(plain.Files) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "files", 1) + } + if plain.Volumes != nil && len(plain.Volumes) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "volumes", 1) + } + *j = Container(plain) + return nil +} + +// 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") + } + type Plain HttpProbe + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + if plain.HttpHeaders != nil && len(plain.HttpHeaders) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "httpHeaders", 1) + } + *j = HttpProbe(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +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["type"]; !ok || v == nil { + return fmt.Errorf("field type in Resource: required") + } + type Plain Resource + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Resource(plain) + return nil +} + +var enumValues_HttpProbeScheme = []interface{}{ + "HTTP", + "HTTPS", +} + +// 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 +} + +// 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 +} + +// 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 +} + +// List of network ports published by the service. +type WorkloadServicePorts map[string]ServicePort + +// The dependencies needed by the Workload. +type WorkloadResources map[string]Resource + +// UnmarshalJSON implements json.Unmarshaler. +func (j *WorkloadMetadata) 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 WorkloadMetadata: required") + } + type Plain WorkloadMetadata + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = WorkloadMetadata(plain) + return nil +} + +// The service that the workload provides. +type WorkloadService struct { + // List of network ports published by the service. + Ports WorkloadServicePorts `json:"ports,omitempty" yaml:"ports,omitempty" mapstructure:"ports,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +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["target"]; !ok || v == nil { + return fmt.Errorf("field target in ContainerFilesElem: required") + } + type Plain ContainerFilesElem + 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) + } + *j = ContainerFilesElem(plain) + return nil +} + +// 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 +} diff --git a/types/types.go b/types/types.go new file mode 100644 index 0000000..1f99d0a --- /dev/null +++ b/types/types.go @@ -0,0 +1,3 @@ +package types + +//go:generate go run github.com/atombender/go-jsonschema@v0.14.1 -v --schema-output=https://score.dev/schemas/score=types.gen.go --schema-package=https://score.dev/schemas/score=types --schema-root-type=https://score.dev/schemas/score=Workload ../schema/files/score-v1b1.json diff --git a/types/workload.go b/types/workload.go deleted file mode 100644 index eb514e7..0000000 --- a/types/workload.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -Apache Score -Copyright 2020 The Apache Software Foundation - -This product includes software developed at -The Apache Software Foundation (http://www.apache.org/). -*/ -package types - -// WorkloadSpec is a single workload specification. -// -// YAML example: -// -// apiVersion: score.dev/v1b1 -// metadata: -// name: hello-world -// service: -// ports: -// www: -// hostPort: 80 -// protocol: TCP -// port: 8080 -// containers: -// hello: -// image: busybox -// command: ["/bin/echo"] -// args: ["Hello $(FRIEND)"] -// variables: -// FRIEND: World! -// resources: -// limits: -// memory: "128Mi" -// cpu: "500m" -// requests: -// memory: "64Mi" -// cpu: "250m" -// livenessProbe: -// httpGet: -// path: /health -// port: 8080 -// httpHeaders: -// - name: Custom-Header -// value: Awesome -// files: -// - target: etc/hello-world/config.yaml -// mode: "666" -// content: ${resources.env.APP_CONFIG} -// volumes: -// - source: ${resources.data} -// path: sub/path -// target: /mnt/data -// read_only: true -// resources: -// env: -// type: environment -// properties: -// APP_CONFIG: -// dns: -// type: dns -// class: sensitive -// properties: -// domain: -// data: -// type: volume -// class: large -// db: -// type: postgres -// properties: -// host: -// default: localhost -// port: -// default: 5432 -type WorkloadSpec struct { - ApiVersion string `json:"apiVersion"` - Metadata WorkloadMeta `json:"metadata"` - Service ServiceSpec `json:"service"` - Containers ContainersSpecs `json:"containers"` - Resources ResourcesSpecs `json:"resources"` -} - -// WorkloadMeta is a workload metadata. -type WorkloadMeta struct { - Name string `json:"name"` -}