Skip to content

Commit

Permalink
✨ Inject typed values. (#142)
Browse files Browse the repository at this point in the history
Inject variables using true or _cast_ types when variable references not
embedded in other text.

Consider:
```
metadata:
 provider:
   address: localhost:$(PORT)
   initConfig:
   - providerSpecificConfig:
       mavenInsecure: $(maven.insecure)
       mavenSettingsFile: $(maven.settings.path)
   name: java
 resources:
 - selector: identity:kind=maven
   fields:
   - key: maven.settings.path
     name: settings
     path: /shared/creds/maven/settings.xml
 - selector: setting:key=mvn.insecure.enabled
   fields:
   - key: maven.insecure
     name: value
```

For example:
```
mavenInsecure = $(maven.insecure)
```
$(maven.insecure) has a _true_ type of boolean (as stored in the db) so
it will be injected as:
```yaml
mavenInsecure: true
```
For cases when the _true_ type is different, an optional `type` field
may be used to cast. In the previous example, if maven.insecure was
stored as a string "true" but need to be injected as a boolean, the type
field may be used.
```
 - selector: setting:key=mvn.insecure.enabled
   fields:
   - key: maven.insecure
     name: value
     type: boolean
```

While we're at it, (and to help with testing), support for _default_
values added.
```
 - selector: setting:key=mvn.insecure.enabled
   fields:
   - key: maven.insecure
     name: value
     type: boolean
     default: false
```

---------

Signed-off-by: Jeff Ortel <[email protected]>
(cherry picked from commit 5e4a70d)
  • Loading branch information
jortel authored and dymurray committed Jan 10, 2025
1 parent 2a16ece commit d10461a
Show file tree
Hide file tree
Showing 2 changed files with 247 additions and 22 deletions.
81 changes: 81 additions & 0 deletions cmd/cmd_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"errors"
"testing"

"github.com/onsi/gomega"
Expand Down Expand Up @@ -126,3 +127,83 @@ func TestIncidentSelector(t *testing.T) {
selector = scope.incidentSelector()
g.Expect("(!package||package=a||package=b) && !(package=C||package=D)").To(gomega.Equal(selector))
}

func TestInjectorDefaults(t *testing.T) {
g := gomega.NewGomegaWithT(t)
inj := ResourceInjector{dict: make(map[string]any)}
r := &Resource{
Fields: []Field{
{
Name: "Name",
Key: "person.name",
Default: "Elmer",
},
{
Name: "Age",
Key: "person.age",
},
},
}
err := inj.addDefaults(r)
g.Expect(err).To(gomega.BeNil())
g.Expect(inj.dict[r.Fields[0].Key]).To(gomega.Equal(r.Fields[0].Default))
g.Expect(inj.dict[r.Fields[1].Key]).To(gomega.BeNil())
}

func TestInjectorTypeCast(t *testing.T) {
g := gomega.NewGomegaWithT(t)
inj := ResourceInjector{dict: make(map[string]any)}
r := &Resource{
Fields: []Field{
{
Name: "Name",
Key: "person.name",
Default: "Elmer",
},
{
Name: "Age",
Key: "person.age",
Type: "integer",
Default: "18",
},
{
Name: "Resident",
Key: "person.resident",
Type: "boolean",
Default: "true",
},
{
Name: "Member",
Key: "person.member",
Type: "boolean",
Default: 1,
},
{
Name: "One",
Key: "person.one",
Type: "integer",
Default: true,
},
},
}
err := inj.addDefaults(r)
g.Expect(err).To(gomega.BeNil())
g.Expect(inj.dict[r.Fields[0].Key]).To(gomega.Equal(r.Fields[0].Default))
g.Expect(inj.dict[r.Fields[1].Key]).To(gomega.Equal(18))
g.Expect(inj.dict[r.Fields[2].Key]).To(gomega.BeTrue())
g.Expect(inj.dict[r.Fields[3].Key]).To(gomega.BeTrue())
g.Expect(inj.dict[r.Fields[4].Key]).To(gomega.Equal(1))

// cast error.
inj.dict = make(map[string]any)
r.Fields = append(
r.Fields,
Field{
Name: "Resident",
Key: "person.parent",
Type: "integer",
Default: "true",
})
err = inj.addDefaults(r)
g.Expect(errors.Is(err, &TypeError{})).To(gomega.BeTrue())
}
188 changes: 166 additions & 22 deletions cmd/injector.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import (
"os"
pathlib "path"
"regexp"
"strconv"
"strings"

"github.com/konveyor/analyzer-lsp/provider"
"github.com/konveyor/tackle2-hub/api"
"github.com/konveyor/tackle2-hub/nas"
)

// KeyRegex $(variable)
var (
KeyRegex = regexp.MustCompile(`(\$\()([^)]+)(\))`)
)
Expand Down Expand Up @@ -49,11 +51,92 @@ func (e *FieldNotMatched) Is(err error) (matched bool) {
return
}

// TypeError used to report resource field cast error.
type TypeError struct {
Field *Field
Reason string
Object any
}

func (e *TypeError) Error() (s string) {
return fmt.Sprintf(
"Resource injector: cast failed. field=%s type=%s reason=%s, object:%v",
e.Field.Name,
e.Field.Type,
e.Reason,
e.Object)
}

func (e *TypeError) Is(err error) (matched bool) {
var inst *TypeError
matched = errors.As(err, &inst)
return
}

// Field injection specification.
type Field struct {
Name string `json:"name"`
Path string `json:"path"`
Key string `json:"key"`
Name string `json:"name"`
Path string `json:"path"`
Key string `json:"key"`
Type string `json:"type"`
Default any `json:"default"`
}

// cast returns object cast as defined by Field.Type.
func (f *Field) cast(object any) (cast any, err error) {
cast = object
if f.Type == "" {
return
}
defer func() {
if err != nil {
err = &TypeError{
Field: f,
Reason: err.Error(),
Object: object,
}
}
}()
switch strings.ToLower(f.Type) {
case "string":
cast = fmt.Sprintf("%v", object)
case "integer":
switch x := object.(type) {
case int,
int8,
int16,
int32,
int64:
cast = x
case bool:
cast = 0
if x {
cast = 1
}
case string:
cast, err = strconv.Atoi(x)
default:
err = errors.New("expected: integer|boolean|string")
}
case "boolean":
switch x := object.(type) {
case bool:
cast = x
case int,
int8,
int16,
int32,
int64:
cast = x != 0
case string:
cast, err = strconv.ParseBool(x)
default:
err = errors.New("expected: integer|boolean|string")
}
default:
err = errors.New("expected: integer|boolean|string")
}
return
}

// Resource injection specification.
Expand Down Expand Up @@ -97,8 +180,27 @@ func (p *ParsedSelector) With(s string) {
}

// ResourceInjector inject resources into extension metadata.
// Example:
// metadata:
// provider:
// address: localhost:$(PORT)
// initConfig:
// - providerSpecificConfig:
// mavenInsecure: $(maven.insecure)
// mavenSettingsFile: $(maven.settings.path)
// name: java
// resources:
// - selector: identity:kind=maven
// fields:
// - key: maven.settings.path
// name: settings
// path: /shared/creds/maven/settings.xml
// - selector: setting:key=mvn.insecure.enabled
// fields:
// - key: maven.insecure
// name: value
type ResourceInjector struct {
dict map[string]string
dict map[string]any
}

// Inject resources into extension metadata.
Expand Down Expand Up @@ -126,11 +228,14 @@ func (r *ResourceInjector) Inject(extension *api.Extension) (p *provider.Config,

// build builds resource dictionary.
func (r *ResourceInjector) build(md *Metadata) (err error) {
r.dict = make(map[string]string)
r.dict = make(map[string]any)
application, err := addon.Task.Application()
if err != nil {
return
}
for _, resource := range md.Resources {
err = r.addDefaults(&resource)
}
for _, resource := range md.Resources {
parsed := ParsedSelector{}
parsed.With(resource.Selector)
Expand Down Expand Up @@ -165,6 +270,20 @@ func (r *ResourceInjector) build(md *Metadata) (err error) {
return
}

// addDefaults adds defaults when specified.
func (r *ResourceInjector) addDefaults(resource *Resource) (err error) {
for _, f := range resource.Fields {
if f.Default == nil {
continue
}
err = r.addField(&f, f.Default)
if err != nil {
return
}
}
return
}

// add the resource fields specified in the injector.
func (r *ResourceInjector) add(resource *Resource, object any) (err error) {
mp := r.asMap(object)
Expand All @@ -177,30 +296,49 @@ func (r *ResourceInjector) add(resource *Resource, object any) (err error) {
}
return
}
fv := r.string(v)
if f.Path != "" {
err = r.write(f.Path, fv)
if err != nil {
return
}
fv = f.Path
err = r.addField(&f, v)
if err != nil {
return
}
r.dict[f.Key] = fv
}
return
}

// addField adds field to the dict.
// When field has a path defined, the values is written to the
// file and the dict[key] = path.
func (r *ResourceInjector) addField(f *Field, v any) (err error) {
if f.Path != "" {
err = r.write(f.Path, v)
if err != nil {
return
}
v = f.Path
} else {
v, err = f.cast(v)
if err != nil {
return
}
}
r.dict[f.Key] = v
return
}

// write a resource field value to a file.
func (r *ResourceInjector) write(path string, s string) (err error) {
func (r *ResourceInjector) write(path string, object any) (err error) {
err = nas.MkDir(pathlib.Dir(path), 0755)
if err != nil {
return
}
f, err := os.Create(path)
if err == nil {
_, err = f.Write([]byte(s))
_ = f.Close()
if err != nil {
return
}
defer func() {
_ = f.Close()
}()
s := r.string(object)
_, err = f.Write([]byte(s))
return
}

Expand Down Expand Up @@ -249,11 +387,17 @@ func (r *ResourceInjector) inject(in any) (out any) {
if len(match) < 3 {
break
}
node = strings.Replace(
node,
match[0],
r.dict[match[2]],
-1)
v := r.dict[match[2]]
if len(node) > len(match[0]) {
node = strings.Replace(
node,
match[0],
r.string(v),
-1)
} else {
out = v
return
}
}
out = node
default:
Expand Down

0 comments on commit d10461a

Please sign in to comment.