Skip to content

Commit

Permalink
switch to jsonpath
Browse files Browse the repository at this point in the history
  • Loading branch information
karlkfi committed Sep 17, 2021
1 parent c7e2b9b commit d47f057
Show file tree
Hide file tree
Showing 7 changed files with 511 additions and 38 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/onsi/ginkgo v1.16.2
github.com/onsi/gomega v1.12.0
github.com/spf13/cobra v1.1.3
github.com/spyzhov/ajson v0.4.2
github.com/stretchr/testify v1.7.0
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spyzhov/ajson v0.4.2 h1:JMByd/jZApPKDvNsmO90X2WWGbmT2ahDFp73QhZbg3s=
github.com/spyzhov/ajson v0.4.2/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzyqMuVA=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
Expand Down
26 changes: 9 additions & 17 deletions pkg/apply/mutator/apply_time_mutator.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"k8s.io/client-go/dynamic"
"k8s.io/klog/v2"
"sigs.k8s.io/cli-utils/pkg/apply/cache"
"sigs.k8s.io/cli-utils/pkg/kyq"
"sigs.k8s.io/cli-utils/pkg/jsonpath"
"sigs.k8s.io/cli-utils/pkg/object/mutation"
"sigs.k8s.io/yaml"
)
Expand Down Expand Up @@ -78,16 +78,16 @@ func (atm *ApplyTimeMutator) Mutate(ctx context.Context, obj *unstructured.Unstr
// lookup target field in target resource
targetValue, _, err := readFieldValue(obj, sub.TargetPath)
if err != nil {
return mutated, reason, fmt.Errorf("failed to read field %q from target resource (%v): %w", sub.TargetPath, targetRef, err)
return mutated, reason, fmt.Errorf("failed to read field (%s) from target resource (%v): %w", sub.TargetPath, targetRef, err)
}

// lookup source field in source resource
sourceValue, found, err := readFieldValue(sourceObj, sub.SourcePath)
if err != nil {
return mutated, reason, fmt.Errorf("failed to read field %q from source resource (%v): %w", sub.SourcePath, sourceRef, err)
return mutated, reason, fmt.Errorf("failed to read field (%s) from source resource (%v): %w", sub.SourcePath, sourceRef, err)
}
if !found {
return mutated, reason, fmt.Errorf("source field %q not present in source resource (%v)", sub.SourcePath, sourceRef)
return mutated, reason, fmt.Errorf("source field (%s) not present in source resource (%v)", sub.SourcePath, sourceRef)
}

var newValue interface{}
Expand All @@ -111,7 +111,7 @@ func (atm *ApplyTimeMutator) Mutate(ctx context.Context, obj *unstructured.Unstr
newValue = strings.ReplaceAll(targetValueString, sub.Token, sourceValueString)
}

klog.V(5).Infof("substitution on (%v): source=%q, token=%q, old=%q, new=%q",
klog.V(5).Infof("substitution on (%v): source=(%s), token=(%s), old=(%s), new=(%s)",
targetRef, sourceValue, sub.Token, targetValue, newValue)

// update target field in target resource
Expand Down Expand Up @@ -181,13 +181,9 @@ func readFieldValue(obj *unstructured.Unstructured, path string) (interface{}, b
return nil, false, errors.New("empty path expression")
}

if strings.HasPrefix(path, "$.") {
return nil, false, errors.New("path expression starts with '$.': yq expressions should start with '.'")
}

value, found, err := kyq.Get(obj.Object, path)
value, found, err := jsonpath.Get(obj.Object, path)
if err != nil {
return nil, false, fmt.Errorf("failed to read resource field with path expression %q: %w", path, err)
return nil, false, err
}
return value, found, nil
}
Expand All @@ -197,13 +193,9 @@ func writeFieldValue(obj *unstructured.Unstructured, path string, value interfac
return errors.New("empty path expression")
}

if strings.HasPrefix(path, "$.") {
return errors.New("path expression starts with '$.': yq expressions should start with '.'")
}

err := kyq.Set(obj.Object, path, value)
err := jsonpath.Set(obj.Object, path, value)
if err != nil {
return fmt.Errorf("failed to read resource field with path expression %q: %w", path, err)
return err
}
return nil
}
Expand Down
126 changes: 105 additions & 21 deletions pkg/apply/mutator/apply_time_mutator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ metadata:
kind: Ingress
name: ingress1-name
namespace: ingress-namespace
sourcePath: .spec.rules[0].http.paths[0].backend.service.port.number
targetPath: .spec.containers[0].env[0].value
sourcePath: $.spec.rules[0].http.paths[0].backend.service.port.number
targetPath: $.spec.containers[0].env[0].value
token: ${service-port}
spec:
containers:
- name: app
image: example:1.0
ports:
containerPort: 80
- containerPort: 80
env:
- name: SERVICE_PORT
value: ${service-port}
Expand Down Expand Up @@ -90,26 +90,27 @@ metadata:
kind: Ingress
name: ingress1-name
namespace: ingress-namespace
sourcePath: .spec.rules[] | select(.http != null) | .http.paths[] | select(.path == "/old") | .backend.service.port.number
targetPath: .spec.containers[] | select(.name == "app") | .env[] | select(.name == "SERVICE_PORT") | .value
sourcePath: $.spec.rules[?(@.http)].http.paths[?(@.path=="/old")].backend.service.port.number
targetPath: $.spec.containers[?(@.name=="app")].env[?(@.name=="SERVICE_PORT")].value
token: ${service-port}
- sourceRef:
group: networking.k8s.io
kind: Ingress
name: ingress1-name
namespace: ingress-namespace
sourcePath: .spec.rules[] | select(.http != null) | .http.paths[] | select(.path == "/old") | .backend.service.name
targetPath: .spec.containers[] | select(.name == "app") | .env[] | select(.name == "SERVICE_NAME") | .value
sourcePath: $.spec.rules[?(@.http)].http.paths[?(@.path=="/old")].backend.service.name
targetPath: $.spec.containers[?(@.name=="app")].env[?(@.name=="SERVICE_NAME")].value
spec:
containers:
- name: app
image: example:1.0
ports:
containerPort: 80
- containerPort: 80
env:
- name: SERVICE_PORT
value: ${service-port}
- name: SERVICE_NAME
value: "" # field must exist to be mutated
`

var pod3y = `
Expand All @@ -124,30 +125,30 @@ metadata:
kind: ConfigMap
name: map1-name
namespace: map-namespace
sourcePath: .data.image
targetPath: .spec.containers[] | select(.name == "app") | .image
sourcePath: $.data.image
targetPath: $.spec.containers[?(@.name=="app")].image
token: ${app-image}
- sourceRef:
kind: ConfigMap
name: map1-name
namespace: map-namespace
sourcePath: .data.version
targetPath: .spec.containers[] | select(.name == "app") | .image
sourcePath: $.data.version
targetPath: $.spec.containers[?(@.name=="app")].image
token: ${app-version}
- sourceRef:
group: networking.k8s.io
kind: Ingress
name: ingress1-name
namespace: ingress-namespace
sourcePath: .spec.rules[] | select(.http != null) | .http.paths[] | select(.path == "/old") | .backend.service.port.number
targetPath: .spec.containers[] | select(.name == "app") | .env[] | select(.name == "SERVICE_PORT") | .value
sourcePath: $.spec.rules[?(@.http)].http.paths[?(@.path=="/old")].backend.service.port.number
targetPath: $.spec.containers[?(@.name=="app")].env[?(@.name=="SERVICE_PORT")].value
token: ${service-port}
spec:
containers:
- name: app
image: ${app-image}:${app-version}
ports:
containerPort: 80
- containerPort: 80
env:
- name: SERVICE_PORT
value: ${service-port}
Expand Down Expand Up @@ -176,8 +177,8 @@ metadata:
kind: ConfigMap
name: map1-name
namespace: map-namespace
sourcePath: .data
targetPath: .data.json
sourcePath: $.data
targetPath: $.data.json
token: ${map-data-json}
data:
json: "[{\"π\":3.14},${map-data-json}]"
Expand Down Expand Up @@ -208,8 +209,8 @@ metadata:
kind: ConfigMap
name: map4-name
namespace: map-namespace
sourcePath: .data
targetPath: .data
sourcePath: $.data
targetPath: $.data
data:
movie: inception
slogan: we need to go deeper
Expand All @@ -229,8 +230,8 @@ metadata:
kind: Ingress
name: ingress1-name
namespace: ingress-namespace
sourcePath: .spec.rules[0].http.paths[] | select(.path == "/old")
targetPath: .spec.rules[0].http.paths[length]
sourcePath: $.spec.rules[0].http.paths[?(@.path=="/old")]
targetPath: $.spec.rules[0].http.paths[(@.length-1)]
spec:
rules:
- http:
Expand All @@ -242,6 +243,7 @@ spec:
name: new
port:
number: 80
- {} # field must exist to be mutated
`

var joinedPathsYaml = `
Expand All @@ -261,6 +263,70 @@ var joinedPathsYaml = `
number: 80
`

var service1y = `
apiVersion: v1
kind: Service
metadata:
name: service1-name
namespace: service1-namespace
annotations:
config.kubernetes.io/apply-time-mutation: |
- sourceRef:
group: apps
kind: Deployment
name: deployment1-name
namespace: deployment1-namespace
sourcePath: $.spec.template.spec.containers[?(@.name=="tcp-handler")].ports[0].containerPort
targetPath: $.spec.ports[?(@.protocol=="TCP" && @.port==80)].targetPort
- sourceRef:
group: apps
kind: Deployment
name: deployment1-name
namespace: deployment1-namespace
sourcePath: $.spec.template.spec.containers[?(@.name=="udp-handler")].ports[0].containerPort
targetPath: $.spec.ports[?(@.protocol=="UDP" && @.port==80)].targetPort
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 0 # field must exist to be mutated
- protocol: TCP
port: 443
targetPort: 443
- protocol: UDP
port: 80
targetPort: 0 # field must exist to be mutated
`

var deployment1y = `
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment1-name
namespace: deployment1-namespace
spec:
selector:
matchLabels:
app: example
replicas: 2
template:
metadata:
labels:
app: example
spec:
containers:
- name: tcp-handler
image: example-tcp
ports:
- containerPort: 8080
- name: udp-handler
image: example-udp
ports:
- containerPort: 8081
`

type nestedFieldValue struct {
Field []interface{}
Value interface{}
Expand All @@ -276,6 +342,8 @@ func TestMutate(t *testing.T) {
configmap3 := ktestutil.YamlToUnstructured(t, configmap3y)
configmap4 := ktestutil.YamlToUnstructured(t, configmap4y)
ingress2 := ktestutil.YamlToUnstructured(t, ingress2y)
service1 := ktestutil.YamlToUnstructured(t, service1y)
deployment1 := ktestutil.YamlToUnstructured(t, deployment1y)

joinedPaths := make([]interface{}, 0)
err := yaml.Unmarshal([]byte(joinedPathsYaml), &joinedPaths)
Expand Down Expand Up @@ -426,6 +494,22 @@ func TestMutate(t *testing.T) {
},
},
},
"multi-field selector": {
target: service1,
sources: []*unstructured.Unstructured{deployment1, deployment1}, // repeats, because not cached
mutated: true,
reason: expectedReason,
expected: []nestedFieldValue{
{
Field: []interface{}{"spec", "ports", 0, "targetPort"},
Value: 8080,
},
{
Field: []interface{}{"spec", "ports", 2, "targetPort"},
Value: 8081,
},
},
},
}

for name, tc := range tests {
Expand Down
Loading

0 comments on commit d47f057

Please sign in to comment.