Skip to content
This repository has been archived by the owner on Jul 4, 2024. It is now read-only.

Commit

Permalink
Removed resources properties definitions from Score spec (#41)
Browse files Browse the repository at this point in the history
Signed-off-by: Eugene Yarshevich <yarshevich@gmail.com>
  • Loading branch information
sujaya-sys committed Jul 18, 2023
1 parent 9b232c0 commit 58c6598
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 261 deletions.
4 changes: 0 additions & 4 deletions examples/02-environment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ containers:
resources:
env:
type: environment
properties:
NAME:
type: string
default: World
```
To prepare a new Humanitec deployment delta from this `score.yaml` file, use `score-humanitec` CLI tool:
Expand Down
4 changes: 0 additions & 4 deletions examples/02-environment/score.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,3 @@ containers:
resources:
env:
type: environment
properties:
NAME:
type: string
default: World
16 changes: 0 additions & 16 deletions examples/03-dependencies/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,10 @@ containers:
resources:
db:
type: postgres
properties:
host:
default: localhost
port:
default: 5432
name:
default: postgres
user:
secret: true
password:
secret: true
dns:
type: dns
properties:
domain:
backend:
type: service
properties:
name:
port:
```
This example also uses an extensions file, called `humanitec.yaml`, that contains additional hints for `score-humanitec` CLI tool. This information would help the CLI tool to resolve the resources properly.
Expand Down
16 changes: 0 additions & 16 deletions examples/03-dependencies/score.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,7 @@ containers:
resources:
db:
type: postgres
properties:
host:
default: localhost
port:
default: 5432
name:
default: postgres
user:
secret: true
password:
secret: true
dns:
type: dns
properties:
domain:
backend:
type: service
properties:
name:
port:
5 changes: 0 additions & 5 deletions examples/04-extras/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@ containers:
resources:
env:
type: environment
properties:
MESSAGE:
type: string
DATADOG_ENV:
type: string
dns:
type: dns
```
Expand Down
5 changes: 0 additions & 5 deletions examples/04-extras/score.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,5 @@ containers:
resources:
env:
type: environment
properties:
MESSAGE:
type: string
DATADOG_ENV:
type: string
dns:
type: dns
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/golang/mock v1.6.0
github.com/imdario/mergo v0.3.13
github.com/mitchellh/mapstructure v1.5.0
github.com/score-spec/score-go v0.0.0-20230601114155-58fa99cb56f8
github.com/score-spec/score-go v0.0.0-20230615134243-75a810d22ad1
github.com/sendgrid/rest v2.6.9+incompatible
github.com/spf13/cobra v1.6.0
github.com/stretchr/testify v1.8.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/score-spec/score-go v0.0.0-20230601114155-58fa99cb56f8 h1:gIOGix8DrqtGbEEBPimUON83Bk+AVnZRpanCKsWXa3s=
github.com/score-spec/score-go v0.0.0-20230601114155-58fa99cb56f8/go.mod h1:kqDzGrkDasa4D1A9MWgHPVPoRVa+zZgFijYOZNDLSpM=
github.com/score-spec/score-go v0.0.0-20230615134243-75a810d22ad1 h1:i/6Z1cPwKOEJp0jNK3oyh1RCWnGR1Cn4cAorRdCIe2M=
github.com/score-spec/score-go v0.0.0-20230615134243-75a810d22ad1/go.mod h1:kqDzGrkDasa4D1A9MWgHPVPoRVa+zZgFijYOZNDLSpM=
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI=
Expand Down
6 changes: 3 additions & 3 deletions internal/humanitec/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,14 @@ func convertContainerSpec(name string, spec *score.ContainerSpec, context *templ

// ConvertSpec converts SCORE specification into Humanitec deployment delta.
func ConvertSpec(name, envID string, spec *score.WorkloadSpec, ext *extensions.HumanitecExtensionsSpec) (*humanitec.CreateDeploymentDeltaRequest, error) {
context, err := buildContext(spec.Metadata, spec.Resources, ext.Resources)
ctx, err := buildContext(spec.Metadata, spec.Resources, ext.Resources)
if err != nil {
return nil, fmt.Errorf("preparing context: %w", err)
}

var containers = make(map[string]interface{}, len(spec.Containers))
for cName, cSpec := range spec.Containers {
if container, err := convertContainerSpec(cName, &cSpec, &context); err == nil {
if container, err := convertContainerSpec(cName, &cSpec, ctx); err == nil {
containers[cName] = container
} else {
return nil, fmt.Errorf("processing container specification for '%s': %w", cName, err)
Expand Down Expand Up @@ -184,7 +184,7 @@ func ConvertSpec(name, envID string, spec *score.WorkloadSpec, ext *extensions.H
}

if ext != nil && len(ext.Spec) > 0 {
var features = context.SubstituteAll(ext.Spec)
var features = ctx.SubstituteAll(ext.Spec)
if err := mergo.Merge(&workloadSpec, features); err != nil {
return nil, fmt.Errorf("applying workload profile features: %w", err)
}
Expand Down
21 changes: 0 additions & 21 deletions internal/humanitec/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,16 +279,9 @@ func TestScoreConvert(t *testing.T) {
},
},
Type: "environment",
Properties: map[string]score.ResourcePropertySpec{
"DEBUG": {Default: false, Required: false},
"DATADOG_ENV": {},
},
},
"dns": {
Type: "dns",
Properties: map[string]score.ResourcePropertySpec{
"domain": {},
},
Params: map[string]interface{}{
"test": "value",
},
Expand All @@ -303,13 +296,6 @@ func TestScoreConvert(t *testing.T) {
},
},
Type: "postgres",
Properties: map[string]score.ResourcePropertySpec{
"host": {Default: "localhost", Required: true},
"port": {Default: 5432, Required: false},
"name": {Required: true},
"user_name": {Required: true, Secret: true},
"password": {Required: true, Secret: true},
},
Params: map[string]interface{}{
"extensions": map[string]interface{}{
"uuid-ossp": map[string]interface{}{
Expand All @@ -321,10 +307,6 @@ func TestScoreConvert(t *testing.T) {
},
"orders": {
Type: "service",
Properties: map[string]score.ResourcePropertySpec{
"name": {Required: false},
"port": {},
},
},
"external-resource": {
Metadata: score.ResourceMeta{
Expand All @@ -333,9 +315,6 @@ func TestScoreConvert(t *testing.T) {
},
},
Type: "some-type",
Properties: map[string]score.ResourcePropertySpec{
"name": {Required: false},
},
},
},
},
Expand Down
143 changes: 76 additions & 67 deletions internal/humanitec/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"fmt"
"log"
"os"
"strings"

"github.com/mitchellh/mapstructure"

Expand All @@ -19,12 +20,14 @@ import (
)

// templatesContext ia an utility type that provides a context for '${...}' templates substitution
type templatesContext map[string]string
type templatesContext struct {
meta map[string]interface{}
resources score.ResourcesSpecs
extensions extensions.HumanitecResourcesSpecs
}

// buildContext initializes a new templatesContext instance
func buildContext(metadata score.WorkloadMeta, resources score.ResourcesSpecs, ext extensions.HumanitecResourcesSpecs) (templatesContext, error) {
var ctx = make(map[string]string)

func buildContext(metadata score.WorkloadMeta, resources score.ResourcesSpecs, ext extensions.HumanitecResourcesSpecs) (*templatesContext, error) {
var metadataMap = make(map[string]interface{})
if decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
TagName: "json",
Expand All @@ -33,75 +36,26 @@ func buildContext(metadata score.WorkloadMeta, resources score.ResourcesSpecs, e
return nil, err
} else {
decoder.Decode(metadata)
for key, val := range metadataMap {
var ref = fmt.Sprintf("metadata.%s", key)
if _, exists := ctx[ref]; exists {
return nil, fmt.Errorf("ambiguous property reference '%s'", ref)
}
ctx[ref] = fmt.Sprintf("%v", val)
}
}

for resName, res := range resources {
var source string
switch res.Type {
case "environment":
source = "values"
case "service":
source = fmt.Sprintf("modules.%s", resName)
default:
if res.Type == "workload" {
log.Println("Warning: 'workload' is a reserved resource type. Its usage may lead to compatibility issues with future releases of this application.")
}
resId, hasAnnotation := res.Metadata.Annotations[AnnotationLabelResourceId]
// DEPRECATED: Should use resource annotations instead
if resExt, hasMeta := ext[resName]; hasMeta && !hasAnnotation {
if resExt.Scope == "" || resExt.Scope == "external" {
resId = fmt.Sprintf("externals.%s", resName)
} else if resExt.Scope == "shared" {
resId = fmt.Sprintf("shared.%s", resName)
}
}
// END (DEPRECATED)

if resId != "" {
source = resId
} else {
source = fmt.Sprintf("externals.%s", resName)
}
}
ctx[fmt.Sprintf("resources.%s", resName)] = source

for propName := range res.Properties {
var ref = fmt.Sprintf("resources.%s.%s", resName, propName)
if _, exists := ctx[ref]; exists {
return nil, fmt.Errorf("ambiguous property reference '%s'", ref)
}
var sourceProp string
switch res.Type {
case "service":
sourceProp = fmt.Sprintf("service.%s", propName)
default:
sourceProp = propName
}
ctx[ref] = fmt.Sprintf("${%s.%s}", source, sourceProp)
}
}

return ctx, nil
return &templatesContext{
meta: metadataMap,
resources: resources,
extensions: ext,
}, nil
}

// SubstituteAll replaces all matching '${...}' templates in map keys and string values recursively.
func (context templatesContext) SubstituteAll(src map[string]interface{}) map[string]interface{} {
func (ctx *templatesContext) SubstituteAll(src map[string]interface{}) map[string]interface{} {
var dst = make(map[string]interface{}, 0)

for key, val := range src {
key = context.Substitute(key)
key = ctx.Substitute(key)
switch v := val.(type) {
case string:
val = context.Substitute(v)
val = ctx.Substitute(v)
case map[string]interface{}:
val = context.SubstituteAll(v)
val = ctx.SubstituteAll(v)
}
dst[key] = val
}
Expand All @@ -110,13 +64,13 @@ func (context templatesContext) SubstituteAll(src map[string]interface{}) map[st
}

// Substitute replaces all matching '${...}' templates in a source string
func (context templatesContext) Substitute(src string) string {
return os.Expand(src, context.mapVar)
func (ctx *templatesContext) Substitute(src string) string {
return os.Expand(src, ctx.mapVar)
}

// MapVar replaces objects and properties references with corresponding values
// Returns an empty string if the reference can't be resolved
func (context templatesContext) mapVar(ref string) string {
func (ctx *templatesContext) mapVar(ref string) string {
if ref == "" {
return ""
}
Expand All @@ -130,8 +84,63 @@ func (context templatesContext) mapVar(ref string) string {
return ref
}

if res, ok := context[ref]; ok {
return res
var segments = strings.SplitN(ref, ".", 2)
switch segments[0] {
case "metadata":
if len(segments) == 2 {
if val, exists := ctx.meta[segments[1]]; exists {
return fmt.Sprintf("%v", val)
}
}

case "resources":
if len(segments) == 2 {
segments = strings.SplitN(segments[1], ".", 2)
var resName = segments[0]
if res, exists := ctx.resources[resName]; exists {
var source string
switch res.Type {
case "environment":
source = "values"
case "service":
source = fmt.Sprintf("modules.%s", resName)
default:
if res.Type == "workload" {
log.Println("Warning: 'workload' is a reserved resource type. Its usage may lead to compatibility issues with future releases of this application.")
}
resId, hasAnnotation := res.Metadata.Annotations[AnnotationLabelResourceId]
// DEPRECATED: Should use resource annotations instead
if resExt, hasMeta := ctx.extensions[resName]; hasMeta && !hasAnnotation {
if resExt.Scope == "" || resExt.Scope == "external" {
resId = fmt.Sprintf("externals.%s", resName)
} else if resExt.Scope == "shared" {
resId = fmt.Sprintf("shared.%s", resName)
}
}
// END (DEPRECATED)

if resId != "" {
source = resId
} else {
source = fmt.Sprintf("externals.%s", resName)
}
}

if len(segments) == 1 {
return source
} else {
var propName = segments[1]
var sourceProp string
switch res.Type {
case "service":
sourceProp = fmt.Sprintf("service.%s", propName)
default:
sourceProp = propName
}
return fmt.Sprintf("${%s.%s}", source, sourceProp)
}
}
}
}

log.Printf("Warning: Can not resolve '%s'. Resource or property is not declared.", ref)
Expand Down
Loading

0 comments on commit 58c6598

Please sign in to comment.