Refactor values composition to use `pkg/chartutil`
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
This commit is contained in:
parent
f48671c020
commit
9c58e02b62
|
|
@ -4,7 +4,7 @@ go 1.22.7
|
|||
|
||||
require (
|
||||
github.com/fluxcd/pkg/apis/kustomize v1.7.0
|
||||
github.com/fluxcd/pkg/apis/meta v1.7.0
|
||||
github.com/fluxcd/pkg/apis/meta v1.8.0
|
||||
k8s.io/apiextensions-apiserver v0.31.3
|
||||
k8s.io/apimachinery v0.31.3
|
||||
sigs.k8s.io/controller-runtime v0.19.3
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
|||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fluxcd/pkg/apis/kustomize v1.7.0 h1:4N23LccihQ3Ose/1FYZGwhrFSh63C9uOVFhwmSInOfQ=
|
||||
github.com/fluxcd/pkg/apis/kustomize v1.7.0/go.mod h1:CqWLBcY2ZPW5f3k2sEypSfjIhz2hFs70PTTYIdKTMaY=
|
||||
github.com/fluxcd/pkg/apis/meta v1.7.0 h1:pDbPrBGgsiWV4bx8j+hodwv1Ysbj/pHP+FH46aTZOfs=
|
||||
github.com/fluxcd/pkg/apis/meta v1.7.0/go.mod h1:OJGH7I//SNO6zcso80oBRuf5H8oU8etZDeTgCcH7qHo=
|
||||
github.com/fluxcd/pkg/apis/meta v1.8.0 h1:wF7MJ3mu5ds9Y/exWU1yU0YyDb8s1VwwQnZYuMWli3c=
|
||||
github.com/fluxcd/pkg/apis/meta v1.8.0/go.mod h1:OJGH7I//SNO6zcso80oBRuf5H8oU8etZDeTgCcH7qHo=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ type HelmReleaseSpec struct {
|
|||
|
||||
// ValuesFrom holds references to resources containing Helm values for this HelmRelease,
|
||||
// and information about how they should be merged.
|
||||
ValuesFrom []ValuesReference `json:"valuesFrom,omitempty"`
|
||||
ValuesFrom []meta.ValuesReference `json:"valuesFrom,omitempty"`
|
||||
|
||||
// Values holds the values for this Helm release.
|
||||
// +optional
|
||||
|
|
|
|||
|
|
@ -68,48 +68,3 @@ type CrossNamespaceSourceReference struct {
|
|||
// +optional
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
// ValuesReference contains a reference to a resource containing Helm values,
|
||||
// and optionally the key they can be found at.
|
||||
type ValuesReference struct {
|
||||
// Kind of the values referent, valid values are ('Secret', 'ConfigMap').
|
||||
// +kubebuilder:validation:Enum=Secret;ConfigMap
|
||||
// +required
|
||||
Kind string `json:"kind"`
|
||||
|
||||
// Name of the values referent. Should reside in the same namespace as the
|
||||
// referring resource.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=253
|
||||
// +required
|
||||
Name string `json:"name"`
|
||||
|
||||
// ValuesKey is the data key where the values.yaml or a specific value can be
|
||||
// found at. Defaults to 'values.yaml'.
|
||||
// +kubebuilder:validation:MaxLength=253
|
||||
// +kubebuilder:validation:Pattern=`^[\-._a-zA-Z0-9]+$`
|
||||
// +optional
|
||||
ValuesKey string `json:"valuesKey,omitempty"`
|
||||
|
||||
// TargetPath is the YAML dot notation path the value should be merged at. When
|
||||
// set, the ValuesKey is expected to be a single flat value. Defaults to 'None',
|
||||
// which results in the values getting merged at the root.
|
||||
// +kubebuilder:validation:MaxLength=250
|
||||
// +kubebuilder:validation:Pattern=`^([a-zA-Z0-9_\-.\\\/]|\[[0-9]{1,5}\])+$`
|
||||
// +optional
|
||||
TargetPath string `json:"targetPath,omitempty"`
|
||||
|
||||
// Optional marks this ValuesReference as optional. When set, a not found error
|
||||
// for the values reference is ignored, but any ValuesKey, TargetPath or
|
||||
// transient error will still result in a reconciliation failure.
|
||||
// +optional
|
||||
Optional bool `json:"optional,omitempty"`
|
||||
}
|
||||
|
||||
// GetValuesKey returns the defined ValuesKey, or the default ('values.yaml').
|
||||
func (in ValuesReference) GetValuesKey() string {
|
||||
if in.ValuesKey == "" {
|
||||
return "values.yaml"
|
||||
}
|
||||
return in.ValuesKey
|
||||
}
|
||||
|
|
|
|||
|
|
@ -326,7 +326,7 @@ func (in *HelmReleaseSpec) DeepCopyInto(out *HelmReleaseSpec) {
|
|||
}
|
||||
if in.ValuesFrom != nil {
|
||||
in, out := &in.ValuesFrom, &out.ValuesFrom
|
||||
*out = make([]ValuesReference, len(*in))
|
||||
*out = make([]meta.ValuesReference, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Values != nil {
|
||||
|
|
@ -717,18 +717,3 @@ func (in *UpgradeRemediation) DeepCopy() *UpgradeRemediation {
|
|||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ValuesReference) DeepCopyInto(out *ValuesReference) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValuesReference.
|
||||
func (in *ValuesReference) DeepCopy() *ValuesReference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ValuesReference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -350,8 +350,8 @@ Uninstall
|
|||
<td>
|
||||
<code>valuesFrom</code><br>
|
||||
<em>
|
||||
<a href="#helm.toolkit.fluxcd.io/v2.ValuesReference">
|
||||
[]ValuesReference
|
||||
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#ValuesReference">
|
||||
[]github.com/fluxcd/pkg/apis/meta.ValuesReference
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
|
|
@ -1360,8 +1360,8 @@ Uninstall
|
|||
<td>
|
||||
<code>valuesFrom</code><br>
|
||||
<em>
|
||||
<a href="#helm.toolkit.fluxcd.io/v2.ValuesReference">
|
||||
[]ValuesReference
|
||||
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#ValuesReference">
|
||||
[]github.com/fluxcd/pkg/apis/meta.ValuesReference
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
|
|
@ -2887,92 +2887,6 @@ RemediationStrategy
|
|||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="helm.toolkit.fluxcd.io/v2.ValuesReference">ValuesReference
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#helm.toolkit.fluxcd.io/v2.HelmReleaseSpec">HelmReleaseSpec</a>)
|
||||
</p>
|
||||
<p>ValuesReference contains a reference to a resource containing Helm values,
|
||||
and optionally the key they can be found at.</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>kind</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Kind of the values referent, valid values are (‘Secret’, ‘ConfigMap’).</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>name</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Name of the values referent. Should reside in the same namespace as the
|
||||
referring resource.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>valuesKey</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>ValuesKey is the data key where the values.yaml or a specific value can be
|
||||
found at. Defaults to ‘values.yaml’.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>targetPath</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>TargetPath is the YAML dot notation path the value should be merged at. When
|
||||
set, the ValuesKey is expected to be a single flat value. Defaults to ‘None’,
|
||||
which results in the values getting merged at the root.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>optional</code><br>
|
||||
<em>
|
||||
bool
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Optional marks this ValuesReference as optional. When set, a not found error
|
||||
for the values reference is ignored, but any ValuesKey, TargetPath or
|
||||
transient error will still result in a reconciliation failure.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admonition note">
|
||||
<p class="last">This page was automatically generated with <code>gen-crd-api-reference-docs</code></p>
|
||||
</div>
|
||||
|
|
|
|||
5
go.mod
5
go.mod
|
|
@ -21,8 +21,9 @@ require (
|
|||
github.com/fluxcd/pkg/apis/acl v0.4.0
|
||||
github.com/fluxcd/pkg/apis/event v0.11.0
|
||||
github.com/fluxcd/pkg/apis/kustomize v1.7.0
|
||||
github.com/fluxcd/pkg/apis/meta v1.7.0
|
||||
github.com/fluxcd/pkg/runtime v0.50.0
|
||||
github.com/fluxcd/pkg/apis/meta v1.8.0
|
||||
github.com/fluxcd/pkg/chartutil v1.0.0
|
||||
github.com/fluxcd/pkg/runtime v0.50.1
|
||||
github.com/fluxcd/pkg/ssa v0.41.1
|
||||
github.com/fluxcd/pkg/testserver v0.8.0
|
||||
github.com/fluxcd/source-controller/api v1.4.1
|
||||
|
|
|
|||
10
go.sum
10
go.sum
|
|
@ -113,10 +113,12 @@ github.com/fluxcd/pkg/apis/event v0.11.0 h1:blvUbgko8EqqjMn1mju2U8aBXUntn3EWbMNc
|
|||
github.com/fluxcd/pkg/apis/event v0.11.0/go.mod h1:AjoDg8Au7RpZbk8B5t3Q2Kq/6kXgmhtdXz6P1y2teAU=
|
||||
github.com/fluxcd/pkg/apis/kustomize v1.7.0 h1:4N23LccihQ3Ose/1FYZGwhrFSh63C9uOVFhwmSInOfQ=
|
||||
github.com/fluxcd/pkg/apis/kustomize v1.7.0/go.mod h1:CqWLBcY2ZPW5f3k2sEypSfjIhz2hFs70PTTYIdKTMaY=
|
||||
github.com/fluxcd/pkg/apis/meta v1.7.0 h1:pDbPrBGgsiWV4bx8j+hodwv1Ysbj/pHP+FH46aTZOfs=
|
||||
github.com/fluxcd/pkg/apis/meta v1.7.0/go.mod h1:OJGH7I//SNO6zcso80oBRuf5H8oU8etZDeTgCcH7qHo=
|
||||
github.com/fluxcd/pkg/runtime v0.50.0 h1:FKJQaOFv8SKp/t7yRE0EkHxA4RIr650SGTLJa1HY3AU=
|
||||
github.com/fluxcd/pkg/runtime v0.50.0/go.mod h1:NEjX8/1DL8B/dsjH1/FD9PjCLPhgdvsffSvzuFrgjys=
|
||||
github.com/fluxcd/pkg/apis/meta v1.8.0 h1:wF7MJ3mu5ds9Y/exWU1yU0YyDb8s1VwwQnZYuMWli3c=
|
||||
github.com/fluxcd/pkg/apis/meta v1.8.0/go.mod h1:OJGH7I//SNO6zcso80oBRuf5H8oU8etZDeTgCcH7qHo=
|
||||
github.com/fluxcd/pkg/chartutil v1.0.0 h1:Hj5mPiUp/nanZPVK7Ur0TDN4BCMhuoxKjvAmBbnX7DE=
|
||||
github.com/fluxcd/pkg/chartutil v1.0.0/go.mod h1:GBo3G78aiK48BppJ/YoDUv8L1NDLHrMpK3K5uiazQ0A=
|
||||
github.com/fluxcd/pkg/runtime v0.50.1 h1:VQeIJ2iq/BjsboGATRTCUQYMU737R0DboKXWVGyBhAI=
|
||||
github.com/fluxcd/pkg/runtime v0.50.1/go.mod h1:lBLhK6y/3kppfBsqmBs8wZ97dEmd44WzLp0iCci4DnY=
|
||||
github.com/fluxcd/pkg/ssa v0.41.1 h1:VW87zsLYAKUvCxJhuEH7VzxVh3SxaU+PyApCT6gKjTk=
|
||||
github.com/fluxcd/pkg/ssa v0.41.1/go.mod h1:7cbyLHqFd5FpcKvhxbHG3DkMm3cZteW45Mi78B0hg8g=
|
||||
github.com/fluxcd/pkg/testserver v0.8.0 h1:ndlCjNpIueEmsLbyg97Dbkq/0Mfzxn4Kq4HSPEb71V8=
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import (
|
|||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
intchartutil "github.com/fluxcd/helm-controller/internal/chartutil"
|
||||
intchartutil "github.com/fluxcd/pkg/chartutil"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ import (
|
|||
helmdriver "helm.sh/helm/v3/pkg/storage/driver"
|
||||
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
"github.com/fluxcd/helm-controller/internal/chartutil"
|
||||
"github.com/fluxcd/helm-controller/internal/release"
|
||||
"github.com/fluxcd/pkg/chartutil"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package chartutil
|
||||
|
||||
import (
|
||||
"github.com/opencontainers/go-digest"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
|
||||
intyaml "github.com/fluxcd/helm-controller/internal/yaml"
|
||||
)
|
||||
|
||||
// DigestValues calculates the digest of the values using the provided algorithm.
|
||||
// The caller is responsible for ensuring that the algorithm is supported.
|
||||
func DigestValues(algo digest.Algorithm, values chartutil.Values) digest.Digest {
|
||||
digester := algo.Digester()
|
||||
if values = valuesOrNil(values); values != nil {
|
||||
if err := intyaml.Encode(digester.Hash(), values, intyaml.SortMapSlice); err != nil {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return digester.Digest()
|
||||
}
|
||||
|
||||
// VerifyValues verifies the digest of the values against the provided digest.
|
||||
func VerifyValues(digest digest.Digest, values chartutil.Values) bool {
|
||||
if digest.Validate() != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
verifier := digest.Verifier()
|
||||
if values = valuesOrNil(values); values != nil {
|
||||
if err := intyaml.Encode(verifier, values, intyaml.SortMapSlice); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return verifier.Verified()
|
||||
}
|
||||
|
||||
// valuesOrNil returns nil if the values are empty, otherwise the values are
|
||||
// returned. This is used to ensure that the digest is calculated against nil
|
||||
// opposed to an empty object.
|
||||
func valuesOrNil(values chartutil.Values) chartutil.Values {
|
||||
if values != nil && len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
|
@ -1,244 +0,0 @@
|
|||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package chartutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
)
|
||||
|
||||
func TestDigestValues(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
algo digest.Algorithm
|
||||
values chartutil.Values
|
||||
want digest.Digest
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
algo: digest.SHA256,
|
||||
values: chartutil.Values{},
|
||||
want: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
},
|
||||
{
|
||||
name: "nil",
|
||||
algo: digest.SHA256,
|
||||
values: nil,
|
||||
want: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
},
|
||||
{
|
||||
name: "value map",
|
||||
algo: digest.SHA256,
|
||||
values: chartutil.Values{
|
||||
"replicas": 3,
|
||||
"image": map[string]interface{}{
|
||||
"tag": "latest",
|
||||
"repository": "nginx",
|
||||
},
|
||||
"ports": []interface{}{
|
||||
map[string]interface{}{
|
||||
"protocol": "TCP",
|
||||
"port": 8080,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"port": 9090,
|
||||
"protocol": "UDP",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6",
|
||||
},
|
||||
{
|
||||
name: "value map in different order",
|
||||
algo: digest.SHA256,
|
||||
values: chartutil.Values{
|
||||
"image": map[string]interface{}{
|
||||
"repository": "nginx",
|
||||
"tag": "latest",
|
||||
},
|
||||
"ports": []interface{}{
|
||||
map[string]interface{}{
|
||||
"port": 8080,
|
||||
"protocol": "TCP",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"port": 9090,
|
||||
"protocol": "UDP",
|
||||
},
|
||||
},
|
||||
"replicas": 3,
|
||||
},
|
||||
want: "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6",
|
||||
},
|
||||
{
|
||||
// Explicit test for something that does not work with sigs.k8s.io/yaml.
|
||||
// See: https://go.dev/play/p/KRyfK9ZobZx
|
||||
name: "values map with numeric keys",
|
||||
algo: digest.SHA256,
|
||||
values: chartutil.Values{
|
||||
"replicas": 3,
|
||||
"test": map[string]interface{}{
|
||||
"632bd80235a05f4192aefade": "value1",
|
||||
"632bd80ddf416cf32fd50679": "value2",
|
||||
"632bd817c559818a52307da2": "value3",
|
||||
"632bd82398e71231a98004b6": "value4",
|
||||
},
|
||||
},
|
||||
want: "sha256:8a980fcbeadd6f05818f07e8aec14070c22250ca3d96af1fcd5f93b3e85b4d70",
|
||||
},
|
||||
{
|
||||
name: "values map with numeric keys in different order",
|
||||
algo: digest.SHA256,
|
||||
values: chartutil.Values{
|
||||
"test": map[string]interface{}{
|
||||
"632bd82398e71231a98004b6": "value4",
|
||||
"632bd817c559818a52307da2": "value3",
|
||||
"632bd80ddf416cf32fd50679": "value2",
|
||||
"632bd80235a05f4192aefade": "value1",
|
||||
},
|
||||
"replicas": 3,
|
||||
},
|
||||
want: "sha256:8a980fcbeadd6f05818f07e8aec14070c22250ca3d96af1fcd5f93b3e85b4d70",
|
||||
},
|
||||
{
|
||||
name: "using different algorithm",
|
||||
algo: digest.SHA512,
|
||||
values: chartutil.Values{
|
||||
"foo": "bar",
|
||||
"baz": map[string]interface{}{
|
||||
"cool": "stuff",
|
||||
},
|
||||
},
|
||||
want: "sha512:b5f9cd4855ca3b08afd602557f373069b1732ce2e6d52341481b0d38f1938452e9d7759ab177c66699962b592f20ceded03eea3cd405d8670578c47842e2c550",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := DigestValues(tt.algo, tt.values); got != tt.want {
|
||||
t.Errorf("DigestValues() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyValues(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
digest digest.Digest
|
||||
values chartutil.Values
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "empty values",
|
||||
digest: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
values: chartutil.Values{},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "nil values",
|
||||
digest: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
values: nil,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty digest",
|
||||
digest: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "invalid digest",
|
||||
digest: "sha512:invalid",
|
||||
values: nil,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "matching values",
|
||||
digest: "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6",
|
||||
values: chartutil.Values{
|
||||
"image": map[string]interface{}{
|
||||
"repository": "nginx",
|
||||
"tag": "latest",
|
||||
},
|
||||
"ports": []interface{}{
|
||||
map[string]interface{}{
|
||||
"port": 8080,
|
||||
"protocol": "TCP",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"port": 9090,
|
||||
"protocol": "UDP",
|
||||
},
|
||||
},
|
||||
"replicas": 3,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "matching values in different order",
|
||||
digest: "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6",
|
||||
values: chartutil.Values{
|
||||
"replicas": 3,
|
||||
"image": map[string]interface{}{
|
||||
"tag": "latest",
|
||||
"repository": "nginx",
|
||||
},
|
||||
"ports": []interface{}{
|
||||
map[string]interface{}{
|
||||
"protocol": "TCP",
|
||||
"port": 8080,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"port": 9090,
|
||||
"protocol": "UDP",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "matching values with numeric keys",
|
||||
digest: "sha256:8a980fcbeadd6f05818f07e8aec14070c22250ca3d96af1fcd5f93b3e85b4d70",
|
||||
values: chartutil.Values{
|
||||
"replicas": 3,
|
||||
"test": map[string]interface{}{
|
||||
"632bd80235a05f4192aefade": "value1",
|
||||
"632bd80ddf416cf32fd50679": "value2",
|
||||
"632bd817c559818a52307da2": "value3",
|
||||
"632bd82398e71231a98004b6": "value4",
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "mismatching values",
|
||||
digest: "sha256:3f3641788a2d4abda3534eaa90c90b54916e4c6e3a5b2e1b24758b7bfa701ecd",
|
||||
values: chartutil.Values{
|
||||
"foo": "bar",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := VerifyValues(tt.digest, tt.values); got != tt.want {
|
||||
t.Errorf("VerifyValues() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,275 +0,0 @@
|
|||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package chartutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
"helm.sh/helm/v3/pkg/strvals"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
kubeclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/fluxcd/pkg/runtime/transform"
|
||||
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
)
|
||||
|
||||
// ErrValuesRefReason is the descriptive reason for an ErrValuesReference.
|
||||
type ErrValuesRefReason error
|
||||
|
||||
var (
|
||||
// ErrResourceNotFound signals the referenced values resource could not be
|
||||
// found.
|
||||
ErrResourceNotFound = errors.New("resource not found")
|
||||
// ErrKeyNotFound signals the key could not be found in the referenced
|
||||
// values resource.
|
||||
ErrKeyNotFound = errors.New("key not found")
|
||||
// ErrUnsupportedRefKind signals the values reference kind is not
|
||||
// supported.
|
||||
ErrUnsupportedRefKind = errors.New("unsupported values reference kind")
|
||||
// ErrValuesDataRead signals the referenced resource's values data could
|
||||
// not be read.
|
||||
ErrValuesDataRead = errors.New("failed to read values data")
|
||||
// ErrValueMerge signals a single value could not be merged into the
|
||||
// values.
|
||||
ErrValueMerge = errors.New("failed to merge value")
|
||||
// ErrUnknown signals the reason an error occurred is unknown.
|
||||
ErrUnknown = errors.New("unknown error")
|
||||
)
|
||||
|
||||
// ErrValuesReference is returned by ChartValuesFromReferences
|
||||
type ErrValuesReference struct {
|
||||
// Reason for the values reference error. Nil equals ErrUnknown.
|
||||
// Can be used with Is to reason about a returned error:
|
||||
// err := &ErrValuesReference{Reason: ErrResourceNotFound, ...}
|
||||
// errors.Is(err, ErrResourceNotFound)
|
||||
Reason ErrValuesRefReason
|
||||
// Kind of the values reference the error is being reported for.
|
||||
Kind string
|
||||
// Name of the values reference the error is being reported for.
|
||||
Name types.NamespacedName
|
||||
// Key of the values reference the error is being reported for.
|
||||
Key string
|
||||
// Optional indicates if the error is being reported for an optional values
|
||||
// reference.
|
||||
Optional bool
|
||||
// Err contains the further error chain leading to this error, it can be
|
||||
// nil.
|
||||
Err error
|
||||
}
|
||||
|
||||
// Error returns an error string constructed out of the state of
|
||||
// ErrValuesReference.
|
||||
func (e *ErrValuesReference) Error() string {
|
||||
b := strings.Builder{}
|
||||
b.WriteString("could not resolve")
|
||||
if e.Optional {
|
||||
b.WriteString(" optional")
|
||||
}
|
||||
if kind := e.Kind; kind != "" {
|
||||
b.WriteString(" " + kind)
|
||||
}
|
||||
b.WriteString(" chart values reference")
|
||||
if name := e.Name.String(); name != "" {
|
||||
b.WriteString(fmt.Sprintf(" '%s'", name))
|
||||
}
|
||||
if key := e.Key; key != "" {
|
||||
b.WriteString(fmt.Sprintf(" with key '%s'", key))
|
||||
}
|
||||
reason := e.Reason.Error()
|
||||
if reason == "" && e.Err == nil {
|
||||
reason = ErrUnknown.Error()
|
||||
}
|
||||
if e.Err != nil {
|
||||
reason = e.Err.Error()
|
||||
}
|
||||
b.WriteString(": " + reason)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Is returns if target == Reason, or target == Err.
|
||||
// Can be used to Reason about a returned error:
|
||||
//
|
||||
// err := &ErrValuesReference{Reason: ErrResourceNotFound, ...}
|
||||
// errors.Is(err, ErrResourceNotFound)
|
||||
func (e *ErrValuesReference) Is(target error) bool {
|
||||
reason := e.Reason
|
||||
if reason == nil {
|
||||
reason = ErrUnknown
|
||||
}
|
||||
if reason == target {
|
||||
return true
|
||||
}
|
||||
return errors.Is(e.Err, target)
|
||||
}
|
||||
|
||||
// Unwrap returns the wrapped Err.
|
||||
func (e *ErrValuesReference) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// NewErrValuesReference returns a new ErrValuesReference constructed from the
|
||||
// provided values.
|
||||
func NewErrValuesReference(name types.NamespacedName, ref v2.ValuesReference, reason ErrValuesRefReason, err error) *ErrValuesReference {
|
||||
return &ErrValuesReference{
|
||||
Reason: reason,
|
||||
Kind: ref.Kind,
|
||||
Name: name,
|
||||
Key: ref.GetValuesKey(),
|
||||
Optional: ref.Optional,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
kindConfigMap = "ConfigMap"
|
||||
kindSecret = "Secret"
|
||||
)
|
||||
|
||||
// ChartValuesFromReferences attempts to construct new chart values by resolving
|
||||
// the provided references using the client, merging them in the order given.
|
||||
// If provided, the values map is merged in last overwriting values from references,
|
||||
// unless a reference has a targetPath specified, in which case it will overwrite all.
|
||||
// It returns the merged values, or an ErrValuesReference error.
|
||||
func ChartValuesFromReferences(ctx context.Context, client kubeclient.Client, namespace string,
|
||||
values map[string]interface{}, refs ...v2.ValuesReference) (chartutil.Values, error) {
|
||||
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
result := chartutil.Values{}
|
||||
resources := make(map[string]kubeclient.Object)
|
||||
|
||||
for _, ref := range refs {
|
||||
namespacedName := types.NamespacedName{Namespace: namespace, Name: ref.Name}
|
||||
var valuesData []byte
|
||||
|
||||
switch ref.Kind {
|
||||
case kindConfigMap, kindSecret:
|
||||
index := ref.Kind + namespacedName.String()
|
||||
|
||||
resource, ok := resources[index]
|
||||
if !ok {
|
||||
// The resource may not exist, but we want to act on a single version
|
||||
// of the resource in case the values reference is marked as optional.
|
||||
resources[index] = nil
|
||||
|
||||
switch ref.Kind {
|
||||
case kindSecret:
|
||||
resource = &corev1.Secret{}
|
||||
case kindConfigMap:
|
||||
resource = &corev1.ConfigMap{}
|
||||
}
|
||||
|
||||
if resource != nil {
|
||||
if err := client.Get(ctx, namespacedName, resource); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
err := NewErrValuesReference(namespacedName, ref, ErrResourceNotFound, err)
|
||||
if err.Optional {
|
||||
log.Info(err.Error())
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
resources[index] = resource
|
||||
}
|
||||
}
|
||||
|
||||
if resource == nil {
|
||||
if ref.Optional {
|
||||
continue
|
||||
}
|
||||
return nil, NewErrValuesReference(namespacedName, ref, ErrResourceNotFound, nil)
|
||||
}
|
||||
|
||||
switch typedRes := resource.(type) {
|
||||
case *corev1.Secret:
|
||||
data, ok := typedRes.Data[ref.GetValuesKey()]
|
||||
if !ok {
|
||||
err := NewErrValuesReference(namespacedName, ref, ErrKeyNotFound, nil)
|
||||
if ref.Optional {
|
||||
log.Info(err.Error())
|
||||
continue
|
||||
}
|
||||
return nil, NewErrValuesReference(namespacedName, ref, ErrKeyNotFound, nil)
|
||||
}
|
||||
valuesData = data
|
||||
case *corev1.ConfigMap:
|
||||
data, ok := typedRes.Data[ref.GetValuesKey()]
|
||||
if !ok {
|
||||
err := NewErrValuesReference(namespacedName, ref, ErrKeyNotFound, nil)
|
||||
if ref.Optional {
|
||||
log.Info(err.Error())
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
valuesData = []byte(data)
|
||||
default:
|
||||
return nil, NewErrValuesReference(namespacedName, ref, ErrUnsupportedRefKind, nil)
|
||||
}
|
||||
default:
|
||||
return nil, NewErrValuesReference(namespacedName, ref, ErrUnsupportedRefKind, nil)
|
||||
}
|
||||
|
||||
if ref.TargetPath != "" {
|
||||
result = transform.MergeMaps(result, values)
|
||||
|
||||
// TODO(hidde): this is a bit of hack, as it mimics the way the option string is passed
|
||||
// to Helm from a CLI perspective. Given the parser is however not publicly accessible
|
||||
// while it contains all logic around parsing the target path, it is a fair trade-off.
|
||||
if err := ReplacePathValue(result, ref.TargetPath, string(valuesData)); err != nil {
|
||||
return nil, NewErrValuesReference(namespacedName, ref, ErrValueMerge, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
values, err := chartutil.ReadValues(valuesData)
|
||||
if err != nil {
|
||||
return nil, NewErrValuesReference(namespacedName, ref, ErrValuesDataRead, err)
|
||||
}
|
||||
result = transform.MergeMaps(result, values)
|
||||
}
|
||||
return transform.MergeMaps(result, values), nil
|
||||
}
|
||||
|
||||
// ReplacePathValue replaces the value at the dot notation path with the given
|
||||
// value using Helm's string value parser using strvals.ParseInto. Single or
|
||||
// double-quoted values are merged using strvals.ParseIntoString.
|
||||
func ReplacePathValue(values chartutil.Values, path string, value string) error {
|
||||
const (
|
||||
singleQuote = "'"
|
||||
doubleQuote = `"`
|
||||
)
|
||||
isSingleQuoted := strings.HasPrefix(value, singleQuote) && strings.HasSuffix(value, singleQuote)
|
||||
isDoubleQuoted := strings.HasPrefix(value, doubleQuote) && strings.HasSuffix(value, doubleQuote)
|
||||
if isSingleQuoted || isDoubleQuoted {
|
||||
value = strings.Trim(value, singleQuote+doubleQuote)
|
||||
value = path + "=" + value
|
||||
return strvals.ParseIntoString(value, values)
|
||||
}
|
||||
value = path + "=" + value
|
||||
return strvals.ParseInto(value, values)
|
||||
}
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
//go:build gofuzz_libfuzzer
|
||||
// +build gofuzz_libfuzzer
|
||||
|
||||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package chartutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
)
|
||||
|
||||
func FuzzChartValuesFromReferences(f *testing.F) {
|
||||
scheme := testScheme()
|
||||
|
||||
tests := []struct {
|
||||
targetPath string
|
||||
valuesKey string
|
||||
hrValues string
|
||||
createObject bool
|
||||
secretData []byte
|
||||
configData string
|
||||
}{
|
||||
{
|
||||
targetPath: "flat",
|
||||
valuesKey: "custom-values.yaml",
|
||||
secretData: []byte(`flat:
|
||||
nested: value
|
||||
nested: value
|
||||
`),
|
||||
configData: `flat: value
|
||||
nested:
|
||||
configuration: value
|
||||
`,
|
||||
hrValues: `
|
||||
other: values
|
||||
`,
|
||||
createObject: true,
|
||||
},
|
||||
{
|
||||
targetPath: "'flat'",
|
||||
valuesKey: "custom-values.yaml",
|
||||
secretData: []byte(`flat:
|
||||
nested: value
|
||||
nested: value
|
||||
`),
|
||||
configData: `flat: value
|
||||
nested:
|
||||
configuration: value
|
||||
`,
|
||||
hrValues: `
|
||||
other: values
|
||||
`,
|
||||
createObject: true,
|
||||
},
|
||||
{
|
||||
targetPath: "flat[0]",
|
||||
secretData: []byte(``),
|
||||
configData: `flat: value`,
|
||||
hrValues: `
|
||||
other: values
|
||||
`,
|
||||
createObject: true,
|
||||
},
|
||||
{
|
||||
secretData: []byte(`flat:
|
||||
nested: value
|
||||
nested: value
|
||||
`),
|
||||
configData: `flat: value
|
||||
nested:
|
||||
configuration: value
|
||||
`,
|
||||
hrValues: `
|
||||
other: values
|
||||
`,
|
||||
createObject: true,
|
||||
},
|
||||
{
|
||||
targetPath: "some-value",
|
||||
hrValues: `
|
||||
other: values
|
||||
`,
|
||||
createObject: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
f.Add(tt.targetPath, tt.valuesKey, tt.hrValues, tt.createObject, tt.secretData, tt.configData)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T,
|
||||
targetPath, valuesKey, hrValues string, createObject bool, secretData []byte, configData string) {
|
||||
|
||||
// objectName and objectNamespace represent a name reference to a core
|
||||
// Kubernetes object upstream (Secret/ConfigMap) which is validated upstream,
|
||||
// and also validated by us in the OpenAPI-based validation set in
|
||||
// v2.ValuesReference. Therefore, a static value here suffices, and instead
|
||||
// we just play with the objects presence/absence.
|
||||
objectName := "values"
|
||||
objectNamespace := "default"
|
||||
var resources []runtime.Object
|
||||
|
||||
if createObject {
|
||||
resources = append(resources,
|
||||
mockConfigMap(objectName, map[string]string{valuesKey: configData}),
|
||||
mockSecret(objectName, map[string][]byte{valuesKey: secretData}),
|
||||
)
|
||||
}
|
||||
|
||||
references := []v2.ValuesReference{
|
||||
{
|
||||
Kind: kindConfigMap,
|
||||
Name: objectName,
|
||||
ValuesKey: valuesKey,
|
||||
TargetPath: targetPath,
|
||||
},
|
||||
{
|
||||
Kind: kindSecret,
|
||||
Name: objectName,
|
||||
ValuesKey: valuesKey,
|
||||
TargetPath: targetPath,
|
||||
},
|
||||
}
|
||||
|
||||
c := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(resources...)
|
||||
var values chartutil.Values
|
||||
if hrValues != "" {
|
||||
values, _ = chartutil.ReadValues([]byte(hrValues))
|
||||
}
|
||||
|
||||
_, _ = ChartValuesFromReferences(logr.NewContext(context.TODO(), logr.Discard()), c.Build(), objectNamespace, values, references...)
|
||||
})
|
||||
}
|
||||
|
||||
func mockSecret(name string, data map[string][]byte) *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: kindSecret,
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name},
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func mockConfigMap(name string, data map[string]string) *corev1.ConfigMap {
|
||||
return &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: kindConfigMap,
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name},
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func testScheme() *runtime.Scheme {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
_ = v2.AddToScheme(scheme)
|
||||
return scheme
|
||||
}
|
||||
|
|
@ -1,437 +0,0 @@
|
|||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package chartutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
. "github.com/onsi/gomega"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
)
|
||||
|
||||
func TestChartValuesFromReferences(t *testing.T) {
|
||||
scheme := testScheme()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
resources []runtime.Object
|
||||
namespace string
|
||||
references []v2.ValuesReference
|
||||
values string
|
||||
want chartutil.Values
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "merges",
|
||||
resources: []runtime.Object{
|
||||
mockConfigMap("values", map[string]string{
|
||||
"values.yaml": `flat: value
|
||||
nested:
|
||||
configuration: value
|
||||
`,
|
||||
}),
|
||||
mockSecret("values", map[string][]byte{
|
||||
"values.yaml": []byte(`flat:
|
||||
nested: value
|
||||
nested: value
|
||||
`),
|
||||
}),
|
||||
},
|
||||
references: []v2.ValuesReference{
|
||||
{
|
||||
Kind: kindConfigMap,
|
||||
Name: "values",
|
||||
},
|
||||
{
|
||||
Kind: kindSecret,
|
||||
Name: "values",
|
||||
},
|
||||
},
|
||||
values: `
|
||||
other: values
|
||||
`,
|
||||
want: chartutil.Values{
|
||||
"flat": map[string]interface{}{
|
||||
"nested": "value",
|
||||
},
|
||||
"nested": "value",
|
||||
"other": "values",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with target path",
|
||||
resources: []runtime.Object{
|
||||
mockSecret("values", map[string][]byte{"single": []byte("value")}),
|
||||
},
|
||||
references: []v2.ValuesReference{
|
||||
{
|
||||
Kind: kindSecret,
|
||||
Name: "values",
|
||||
ValuesKey: "single",
|
||||
TargetPath: "merge.at.specific.path",
|
||||
},
|
||||
},
|
||||
want: chartutil.Values{
|
||||
"merge": map[string]interface{}{
|
||||
"at": map[string]interface{}{
|
||||
"specific": map[string]interface{}{
|
||||
"path": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "target path precedence over all",
|
||||
resources: []runtime.Object{
|
||||
mockConfigMap("values", map[string]string{
|
||||
"values.yaml": `flat: value
|
||||
nested:
|
||||
configuration:
|
||||
- one
|
||||
- two
|
||||
- three
|
||||
`,
|
||||
}),
|
||||
mockSecret("values", map[string][]byte{"key": []byte("value")}),
|
||||
},
|
||||
references: []v2.ValuesReference{
|
||||
{
|
||||
Kind: kindSecret,
|
||||
Name: "values",
|
||||
ValuesKey: "key",
|
||||
TargetPath: "nested.configuration[0]",
|
||||
},
|
||||
{
|
||||
Kind: kindConfigMap,
|
||||
Name: "values",
|
||||
},
|
||||
},
|
||||
|
||||
values: `
|
||||
nested:
|
||||
configuration:
|
||||
- list
|
||||
- item
|
||||
- option
|
||||
`,
|
||||
want: chartutil.Values{
|
||||
"flat": "value",
|
||||
"nested": map[string]interface{}{
|
||||
"configuration": []interface{}{"value", "item", "option"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "target path for string type array item",
|
||||
resources: []runtime.Object{
|
||||
mockConfigMap("values", map[string]string{
|
||||
"values.yaml": `flat: value
|
||||
nested:
|
||||
configuration:
|
||||
- list
|
||||
- item
|
||||
- option
|
||||
`,
|
||||
}),
|
||||
mockSecret("values", map[string][]byte{
|
||||
"values.yaml": []byte(`foo`),
|
||||
}),
|
||||
},
|
||||
references: []v2.ValuesReference{
|
||||
{
|
||||
Kind: kindConfigMap,
|
||||
Name: "values",
|
||||
},
|
||||
{
|
||||
Kind: kindSecret,
|
||||
Name: "values",
|
||||
TargetPath: "nested.configuration[1]",
|
||||
},
|
||||
},
|
||||
values: `
|
||||
other: values
|
||||
`,
|
||||
want: chartutil.Values{
|
||||
"flat": "value",
|
||||
"nested": map[string]interface{}{
|
||||
"configuration": []interface{}{"list", "foo", "option"},
|
||||
},
|
||||
"other": "values",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "values reference to non existing secret",
|
||||
references: []v2.ValuesReference{
|
||||
{
|
||||
Kind: kindSecret,
|
||||
Name: "missing",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "optional values reference to non existing secret",
|
||||
references: []v2.ValuesReference{
|
||||
{
|
||||
Kind: kindSecret,
|
||||
Name: "missing",
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
want: chartutil.Values{},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "values reference to non existing config map",
|
||||
references: []v2.ValuesReference{
|
||||
{
|
||||
Kind: kindConfigMap,
|
||||
Name: "missing",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "optional values reference to non existing config map",
|
||||
references: []v2.ValuesReference{
|
||||
{
|
||||
Kind: kindConfigMap,
|
||||
Name: "missing",
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
want: chartutil.Values{},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "missing secret key",
|
||||
resources: []runtime.Object{
|
||||
mockSecret("values", nil),
|
||||
},
|
||||
references: []v2.ValuesReference{
|
||||
{
|
||||
Kind: kindSecret,
|
||||
Name: "values",
|
||||
ValuesKey: "nonexisting",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing config map key",
|
||||
resources: []runtime.Object{
|
||||
mockConfigMap("values", nil),
|
||||
},
|
||||
references: []v2.ValuesReference{
|
||||
{
|
||||
Kind: kindConfigMap,
|
||||
Name: "values",
|
||||
ValuesKey: "nonexisting",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "unsupported values reference kind",
|
||||
references: []v2.ValuesReference{
|
||||
{
|
||||
Kind: "Unsupported",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid values",
|
||||
resources: []runtime.Object{
|
||||
mockConfigMap("values", map[string]string{
|
||||
"values.yaml": `
|
||||
invalid`,
|
||||
}),
|
||||
},
|
||||
references: []v2.ValuesReference{
|
||||
{
|
||||
Kind: kindConfigMap,
|
||||
Name: "values",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
c := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(tt.resources...)
|
||||
var values map[string]interface{}
|
||||
if tt.values != "" {
|
||||
m, err := chartutil.ReadValues([]byte(tt.values))
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
values = m
|
||||
}
|
||||
ctx := logr.NewContext(context.TODO(), logr.Discard())
|
||||
got, err := ChartValuesFromReferences(ctx, c.Build(), tt.namespace, values, tt.references...)
|
||||
if tt.wantErr {
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(got).To(BeNil())
|
||||
return
|
||||
}
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(got).To(Equal(tt.want))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// This tests compatability with the formats described in:
|
||||
// https://helm.sh/docs/intro/using_helm/#the-format-and-limitations-of---set
|
||||
func TestReplacePathValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
value []byte
|
||||
path string
|
||||
want map[string]interface{}
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "outer inner",
|
||||
value: []byte("value"),
|
||||
path: "outer.inner",
|
||||
want: map[string]interface{}{
|
||||
"outer": map[string]interface{}{
|
||||
"inner": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "inline list",
|
||||
value: []byte("{a,b,c}"),
|
||||
path: "name",
|
||||
want: map[string]interface{}{
|
||||
// TODO(hidde): figure out why the cap is off by len+1
|
||||
"name": append(make([]interface{}, 0, 4), []interface{}{"a", "b", "c"}...),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with escape",
|
||||
value: []byte(`value1\,value2`),
|
||||
path: "name",
|
||||
want: map[string]interface{}{
|
||||
"name": "value1,value2",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "target path with boolean value",
|
||||
value: []byte("true"),
|
||||
path: "merge.at.specific.path",
|
||||
want: chartutil.Values{
|
||||
"merge": map[string]interface{}{
|
||||
"at": map[string]interface{}{
|
||||
"specific": map[string]interface{}{
|
||||
"path": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "target path with set-string behavior",
|
||||
value: []byte(`"true"`),
|
||||
path: "merge.at.specific.path",
|
||||
want: chartutil.Values{
|
||||
"merge": map[string]interface{}{
|
||||
"at": map[string]interface{}{
|
||||
"specific": map[string]interface{}{
|
||||
"path": "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "target path with array item",
|
||||
value: []byte("value"),
|
||||
path: "merge.at[2]",
|
||||
want: chartutil.Values{
|
||||
"merge": map[string]interface{}{
|
||||
"at": []interface{}{nil, nil, "value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dot sequence escaping path",
|
||||
value: []byte("master"),
|
||||
path: `nodeSelector.kubernetes\.io/role`,
|
||||
want: map[string]interface{}{
|
||||
"nodeSelector": map[string]interface{}{
|
||||
"kubernetes.io/role": "master",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
values := map[string]interface{}{}
|
||||
err := ReplacePathValue(values, tt.path, string(tt.value))
|
||||
if tt.wantErr {
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(values).To(BeNil())
|
||||
return
|
||||
}
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(values).To(Equal(tt.want))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mockSecret(name string, data map[string][]byte) *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: kindSecret,
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name},
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func mockConfigMap(name string, data map[string]string) *corev1.ConfigMap {
|
||||
return &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: kindConfigMap,
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name},
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func testScheme() *runtime.Scheme {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
_ = v2.AddToScheme(scheme)
|
||||
return scheme
|
||||
}
|
||||
|
|
@ -58,10 +58,11 @@ import (
|
|||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
|
||||
"github.com/fluxcd/pkg/chartutil"
|
||||
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
intacl "github.com/fluxcd/helm-controller/internal/acl"
|
||||
"github.com/fluxcd/helm-controller/internal/action"
|
||||
"github.com/fluxcd/helm-controller/internal/chartutil"
|
||||
"github.com/fluxcd/helm-controller/internal/digest"
|
||||
interrors "github.com/fluxcd/helm-controller/internal/errors"
|
||||
"github.com/fluxcd/helm-controller/internal/features"
|
||||
|
|
@ -307,7 +308,12 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe
|
|||
}
|
||||
|
||||
// Compose values based from the spec and references.
|
||||
values, err := chartutil.ChartValuesFromReferences(ctx, r.Client, obj.Namespace, obj.GetValues(), obj.Spec.ValuesFrom...)
|
||||
values, err := chartutil.ChartValuesFromReferences(ctx,
|
||||
log,
|
||||
r.Client,
|
||||
obj.Namespace,
|
||||
obj.GetValues(),
|
||||
obj.Spec.ValuesFrom...)
|
||||
if err != nil {
|
||||
conditions.MarkFalse(obj, meta.ReadyCondition, "ValuesError", "%s", err)
|
||||
r.Eventf(obj, corev1.EventTypeWarning, "ValuesError", err.Error())
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ import (
|
|||
aclv1 "github.com/fluxcd/pkg/apis/acl"
|
||||
"github.com/fluxcd/pkg/apis/kustomize"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/chartutil"
|
||||
"github.com/fluxcd/pkg/runtime/conditions"
|
||||
feathelper "github.com/fluxcd/pkg/runtime/features"
|
||||
"github.com/fluxcd/pkg/runtime/patch"
|
||||
|
|
@ -58,7 +59,6 @@ import (
|
|||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
intacl "github.com/fluxcd/helm-controller/internal/acl"
|
||||
"github.com/fluxcd/helm-controller/internal/action"
|
||||
"github.com/fluxcd/helm-controller/internal/chartutil"
|
||||
"github.com/fluxcd/helm-controller/internal/features"
|
||||
"github.com/fluxcd/helm-controller/internal/kube"
|
||||
"github.com/fluxcd/helm-controller/internal/postrender"
|
||||
|
|
@ -331,7 +331,7 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) {
|
|||
Namespace: "mock",
|
||||
},
|
||||
Spec: v2.HelmReleaseSpec{
|
||||
ValuesFrom: []v2.ValuesReference{
|
||||
ValuesFrom: []meta.ValuesReference{
|
||||
{
|
||||
Kind: "Secret",
|
||||
Name: "missing",
|
||||
|
|
@ -1537,7 +1537,7 @@ func TestHelmReleaseReconciler_reconcileReleaseFromOCIRepositorySource(t *testin
|
|||
Name: "ocirepo",
|
||||
Namespace: "mock",
|
||||
},
|
||||
ValuesFrom: []v2.ValuesReference{
|
||||
ValuesFrom: []meta.ValuesReference{
|
||||
{
|
||||
Kind: "Secret",
|
||||
Name: "missing",
|
||||
|
|
@ -3526,12 +3526,12 @@ func Test_waitForHistoryCacheSync(t *testing.T) {
|
|||
func TestValuesReferenceValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
references []v2.ValuesReference
|
||||
references []meta.ValuesReference
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid ValuesKey",
|
||||
references: []v2.ValuesReference{
|
||||
references: []meta.ValuesReference{
|
||||
{
|
||||
Kind: "Secret",
|
||||
Name: "values",
|
||||
|
|
@ -3542,7 +3542,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "valid ValuesKey: empty",
|
||||
references: []v2.ValuesReference{
|
||||
references: []meta.ValuesReference{
|
||||
{
|
||||
Kind: "Secret",
|
||||
Name: "values",
|
||||
|
|
@ -3553,7 +3553,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "valid ValuesKey: long",
|
||||
references: []v2.ValuesReference{
|
||||
references: []meta.ValuesReference{
|
||||
{
|
||||
Kind: "Secret",
|
||||
Name: "values",
|
||||
|
|
@ -3564,7 +3564,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "invalid ValuesKey",
|
||||
references: []v2.ValuesReference{
|
||||
references: []meta.ValuesReference{
|
||||
{
|
||||
Kind: "Secret",
|
||||
Name: "values",
|
||||
|
|
@ -3575,7 +3575,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "invalid ValuesKey: too long",
|
||||
references: []v2.ValuesReference{
|
||||
references: []meta.ValuesReference{
|
||||
{
|
||||
Kind: "Secret",
|
||||
Name: "values",
|
||||
|
|
@ -3586,7 +3586,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "valid target path: empty",
|
||||
references: []v2.ValuesReference{
|
||||
references: []meta.ValuesReference{
|
||||
{
|
||||
Kind: "Secret",
|
||||
Name: "values",
|
||||
|
|
@ -3597,7 +3597,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "valid target path",
|
||||
references: []v2.ValuesReference{
|
||||
references: []meta.ValuesReference{
|
||||
{
|
||||
Kind: "Secret",
|
||||
Name: "values",
|
||||
|
|
@ -3608,7 +3608,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "valid target path: long",
|
||||
references: []v2.ValuesReference{
|
||||
references: []meta.ValuesReference{
|
||||
{
|
||||
Kind: "Secret",
|
||||
Name: "values",
|
||||
|
|
@ -3619,7 +3619,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "invalid target path: too long",
|
||||
references: []v2.ValuesReference{
|
||||
references: []meta.ValuesReference{
|
||||
{
|
||||
Kind: "Secret",
|
||||
Name: "values",
|
||||
|
|
@ -3630,7 +3630,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "invalid target path: opened index",
|
||||
references: []v2.ValuesReference{
|
||||
references: []meta.ValuesReference{
|
||||
{
|
||||
Kind: "Secret",
|
||||
Name: "values",
|
||||
|
|
@ -3642,7 +3642,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "invalid target path: incorrect index syntax",
|
||||
references: []v2.ValuesReference{
|
||||
references: []meta.ValuesReference{
|
||||
{
|
||||
Kind: "Secret",
|
||||
Name: "values",
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ import (
|
|||
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
"github.com/fluxcd/helm-controller/internal/action"
|
||||
"github.com/fluxcd/helm-controller/internal/chartutil"
|
||||
"github.com/fluxcd/helm-controller/internal/digest"
|
||||
"github.com/fluxcd/pkg/chartutil"
|
||||
)
|
||||
|
||||
// Install is an ActionReconciler which attempts to install a Helm release
|
||||
|
|
|
|||
|
|
@ -40,11 +40,11 @@ import (
|
|||
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
"github.com/fluxcd/helm-controller/internal/action"
|
||||
"github.com/fluxcd/helm-controller/internal/chartutil"
|
||||
"github.com/fluxcd/helm-controller/internal/digest"
|
||||
"github.com/fluxcd/helm-controller/internal/release"
|
||||
"github.com/fluxcd/helm-controller/internal/storage"
|
||||
"github.com/fluxcd/helm-controller/internal/testutil"
|
||||
"github.com/fluxcd/pkg/chartutil"
|
||||
)
|
||||
|
||||
func TestInstall_Reconcile(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -31,10 +31,10 @@ import (
|
|||
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
"github.com/fluxcd/helm-controller/internal/action"
|
||||
"github.com/fluxcd/helm-controller/internal/chartutil"
|
||||
"github.com/fluxcd/helm-controller/internal/digest"
|
||||
"github.com/fluxcd/helm-controller/internal/release"
|
||||
"github.com/fluxcd/helm-controller/internal/storage"
|
||||
"github.com/fluxcd/pkg/chartutil"
|
||||
)
|
||||
|
||||
// RollbackRemediation is an ActionReconciler which attempts to roll back
|
||||
|
|
|
|||
|
|
@ -39,11 +39,11 @@ import (
|
|||
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
"github.com/fluxcd/helm-controller/internal/action"
|
||||
"github.com/fluxcd/helm-controller/internal/chartutil"
|
||||
"github.com/fluxcd/helm-controller/internal/digest"
|
||||
"github.com/fluxcd/helm-controller/internal/release"
|
||||
"github.com/fluxcd/helm-controller/internal/storage"
|
||||
"github.com/fluxcd/helm-controller/internal/testutil"
|
||||
"github.com/fluxcd/pkg/chartutil"
|
||||
)
|
||||
|
||||
func TestRollbackRemediation_Reconcile(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -38,10 +38,10 @@ import (
|
|||
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
"github.com/fluxcd/helm-controller/internal/action"
|
||||
"github.com/fluxcd/helm-controller/internal/chartutil"
|
||||
"github.com/fluxcd/helm-controller/internal/digest"
|
||||
"github.com/fluxcd/helm-controller/internal/release"
|
||||
"github.com/fluxcd/helm-controller/internal/testutil"
|
||||
"github.com/fluxcd/pkg/chartutil"
|
||||
)
|
||||
|
||||
// testHookFixtures is a list of release.Hook in every possible LastRun state.
|
||||
|
|
|
|||
|
|
@ -37,11 +37,11 @@ import (
|
|||
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
"github.com/fluxcd/helm-controller/internal/action"
|
||||
"github.com/fluxcd/helm-controller/internal/chartutil"
|
||||
"github.com/fluxcd/helm-controller/internal/digest"
|
||||
"github.com/fluxcd/helm-controller/internal/release"
|
||||
"github.com/fluxcd/helm-controller/internal/storage"
|
||||
"github.com/fluxcd/helm-controller/internal/testutil"
|
||||
"github.com/fluxcd/pkg/chartutil"
|
||||
)
|
||||
|
||||
func TestUninstallRemediation_Reconcile(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -38,11 +38,11 @@ import (
|
|||
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
"github.com/fluxcd/helm-controller/internal/action"
|
||||
"github.com/fluxcd/helm-controller/internal/chartutil"
|
||||
"github.com/fluxcd/helm-controller/internal/digest"
|
||||
"github.com/fluxcd/helm-controller/internal/release"
|
||||
"github.com/fluxcd/helm-controller/internal/storage"
|
||||
"github.com/fluxcd/helm-controller/internal/testutil"
|
||||
"github.com/fluxcd/pkg/chartutil"
|
||||
)
|
||||
|
||||
func TestUninstall_Reconcile(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -38,11 +38,11 @@ import (
|
|||
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
"github.com/fluxcd/helm-controller/internal/action"
|
||||
"github.com/fluxcd/helm-controller/internal/chartutil"
|
||||
"github.com/fluxcd/helm-controller/internal/digest"
|
||||
"github.com/fluxcd/helm-controller/internal/release"
|
||||
"github.com/fluxcd/helm-controller/internal/storage"
|
||||
"github.com/fluxcd/helm-controller/internal/testutil"
|
||||
"github.com/fluxcd/pkg/chartutil"
|
||||
)
|
||||
|
||||
func TestUnlock_Reconcile(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ import (
|
|||
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
"github.com/fluxcd/helm-controller/internal/action"
|
||||
"github.com/fluxcd/helm-controller/internal/chartutil"
|
||||
"github.com/fluxcd/helm-controller/internal/digest"
|
||||
"github.com/fluxcd/pkg/chartutil"
|
||||
)
|
||||
|
||||
// Upgrade is an ActionReconciler which attempts to upgrade a Helm release
|
||||
|
|
|
|||
|
|
@ -40,11 +40,11 @@ import (
|
|||
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
"github.com/fluxcd/helm-controller/internal/action"
|
||||
"github.com/fluxcd/helm-controller/internal/chartutil"
|
||||
"github.com/fluxcd/helm-controller/internal/digest"
|
||||
"github.com/fluxcd/helm-controller/internal/release"
|
||||
"github.com/fluxcd/helm-controller/internal/storage"
|
||||
"github.com/fluxcd/helm-controller/internal/testutil"
|
||||
"github.com/fluxcd/pkg/chartutil"
|
||||
)
|
||||
|
||||
func TestUpgrade_Reconcile(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
"github.com/fluxcd/helm-controller/internal/chartutil"
|
||||
"github.com/fluxcd/helm-controller/internal/digest"
|
||||
"github.com/fluxcd/pkg/chartutil"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
Copyright 2023 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
goyaml "sigs.k8s.io/yaml/goyaml.v2"
|
||||
)
|
||||
|
||||
// PreEncoder allows for pre-processing of the YAML data before encoding.
|
||||
type PreEncoder func(goyaml.MapSlice)
|
||||
|
||||
// Encode encodes the given data to YAML format and writes it to the provided
|
||||
// io.Write, without going through a byte representation (unlike
|
||||
// sigs.k8s.io/yaml#Unmarshal).
|
||||
//
|
||||
// It optionally takes one or more PreEncoder functions that allow
|
||||
// for pre-processing of the data before encoding, such as sorting the data.
|
||||
//
|
||||
// It returns an error if the data cannot be encoded.
|
||||
func Encode(w io.Writer, data map[string]interface{}, pe ...PreEncoder) error {
|
||||
ms := yaml.JSONObjectToYAMLObject(data)
|
||||
for _, m := range pe {
|
||||
m(ms)
|
||||
}
|
||||
return goyaml.NewEncoder(w).Encode(ms)
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
/*
|
||||
Copyright 2023 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input map[string]interface{}
|
||||
preEncoders []PreEncoder
|
||||
want []byte
|
||||
}{
|
||||
{
|
||||
name: "empty map",
|
||||
input: map[string]interface{}{},
|
||||
want: []byte(`{}
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "simple values",
|
||||
input: map[string]interface{}{
|
||||
"replicaCount": 3,
|
||||
},
|
||||
want: []byte(`replicaCount: 3
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "with pre-encoder",
|
||||
input: map[string]interface{}{
|
||||
"replicaCount": 3,
|
||||
"image": map[string]interface{}{
|
||||
"repository": "nginx",
|
||||
"tag": "latest",
|
||||
},
|
||||
"port": 8080,
|
||||
},
|
||||
preEncoders: []PreEncoder{SortMapSlice},
|
||||
want: []byte(`image:
|
||||
repository: nginx
|
||||
tag: latest
|
||||
port: 8080
|
||||
replicaCount: 3
|
||||
`),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var actual bytes.Buffer
|
||||
err := Encode(&actual, tt.input, tt.preEncoders...)
|
||||
if err != nil {
|
||||
t.Fatalf("error encoding: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(actual.Bytes(), tt.want) {
|
||||
t.Errorf("Encode() = %v, want: %s", actual.String(), tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode(b *testing.B) {
|
||||
// Test against the values.yaml from the kube-prometheus-stack chart, which
|
||||
// is a fairly large file.
|
||||
v, err := os.ReadFile("testdata/values.yaml")
|
||||
if err != nil {
|
||||
b.Fatalf("error reading testdata: %v", err)
|
||||
}
|
||||
|
||||
var data map[string]interface{}
|
||||
if err = yaml.Unmarshal(v, &data); err != nil {
|
||||
b.Fatalf("error unmarshalling testdata: %v", err)
|
||||
}
|
||||
|
||||
b.Run("EncodeWithSort", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Encode(bytes.NewBuffer(nil), data, SortMapSlice)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("SigYAMLMarshal", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
yaml.Marshal(data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
Copyright 2023 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
goyaml "sigs.k8s.io/yaml/goyaml.v2"
|
||||
)
|
||||
|
||||
// SortMapSlice recursively sorts the given goyaml.MapSlice by key.
|
||||
// It can be used in combination with Encode to sort YAML by key
|
||||
// before encoding it.
|
||||
func SortMapSlice(ms goyaml.MapSlice) {
|
||||
sort.Slice(ms, func(i, j int) bool {
|
||||
return ms[i].Key.(string) < ms[j].Key.(string)
|
||||
})
|
||||
|
||||
for _, item := range ms {
|
||||
if nestedMS, ok := item.Value.(goyaml.MapSlice); ok {
|
||||
SortMapSlice(nestedMS)
|
||||
} else if nestedSlice, ok := item.Value.([]interface{}); ok {
|
||||
for _, vItem := range nestedSlice {
|
||||
if nestedMS, ok := vItem.(goyaml.MapSlice); ok {
|
||||
SortMapSlice(nestedMS)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,183 +0,0 @@
|
|||
/*
|
||||
Copyright 2023 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
goyaml "sigs.k8s.io/yaml/goyaml.v2"
|
||||
)
|
||||
|
||||
func TestSortMapSlice(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input map[string]interface{}
|
||||
want map[string]interface{}
|
||||
}{
|
||||
{
|
||||
name: "empty map",
|
||||
input: map[string]interface{}{},
|
||||
want: map[string]interface{}{},
|
||||
},
|
||||
{
|
||||
name: "flat map",
|
||||
input: map[string]interface{}{
|
||||
"b": "value-b",
|
||||
"a": "value-a",
|
||||
"c": "value-c",
|
||||
},
|
||||
want: map[string]interface{}{
|
||||
"a": "value-a",
|
||||
"b": "value-b",
|
||||
"c": "value-c",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested map",
|
||||
input: map[string]interface{}{
|
||||
"b": "value-b",
|
||||
"a": "value-a",
|
||||
"c": map[string]interface{}{
|
||||
"z": "value-z",
|
||||
"y": "value-y",
|
||||
},
|
||||
},
|
||||
want: map[string]interface{}{
|
||||
"a": "value-a",
|
||||
"b": "value-b",
|
||||
"c": map[string]interface{}{
|
||||
"y": "value-y",
|
||||
"z": "value-z",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "map with slices",
|
||||
input: map[string]interface{}{
|
||||
"b": []interface{}{"apple", "banana", "cherry"},
|
||||
"a": []interface{}{"orange", "grape"},
|
||||
"c": []interface{}{"strawberry"},
|
||||
},
|
||||
want: map[string]interface{}{
|
||||
"a": []interface{}{"orange", "grape"},
|
||||
"b": []interface{}{"apple", "banana", "cherry"},
|
||||
"c": []interface{}{"strawberry"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "map with mixed data types",
|
||||
input: map[string]interface{}{
|
||||
"b": 50,
|
||||
"a": "value-a",
|
||||
"c": []interface{}{"strawberry", "banana"},
|
||||
"d": map[string]interface{}{
|
||||
"x": true,
|
||||
"y": 123,
|
||||
},
|
||||
},
|
||||
want: map[string]interface{}{
|
||||
"a": "value-a",
|
||||
"b": 50,
|
||||
"c": []interface{}{"strawberry", "banana"},
|
||||
"d": map[string]interface{}{
|
||||
"x": true,
|
||||
"y": 123,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "map with complex structure",
|
||||
input: map[string]interface{}{
|
||||
"a": map[string]interface{}{
|
||||
"c": "value-c",
|
||||
"b": "value-b",
|
||||
"a": "value-a",
|
||||
},
|
||||
"b": "value-b",
|
||||
"c": map[string]interface{}{
|
||||
"z": map[string]interface{}{
|
||||
"a": "value-a",
|
||||
"b": "value-b",
|
||||
"c": "value-c",
|
||||
},
|
||||
"y": "value-y",
|
||||
},
|
||||
"d": map[string]interface{}{
|
||||
"q": "value-q",
|
||||
"p": "value-p",
|
||||
"r": "value-r",
|
||||
},
|
||||
"e": []interface{}{"strawberry", "banana"},
|
||||
},
|
||||
want: map[string]interface{}{
|
||||
"a": map[string]interface{}{
|
||||
"a": "value-a",
|
||||
"b": "value-b",
|
||||
"c": "value-c",
|
||||
},
|
||||
"b": "value-b",
|
||||
"c": map[string]interface{}{
|
||||
"y": "value-y",
|
||||
"z": map[string]interface{}{
|
||||
"a": "value-a",
|
||||
"b": "value-b",
|
||||
"c": "value-c",
|
||||
},
|
||||
},
|
||||
"d": map[string]interface{}{
|
||||
"p": "value-p",
|
||||
"q": "value-q",
|
||||
"r": "value-r",
|
||||
},
|
||||
"e": []interface{}{"strawberry", "banana"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "map with empty slices and maps",
|
||||
input: map[string]interface{}{
|
||||
"b": []interface{}{},
|
||||
"a": map[string]interface{}{},
|
||||
},
|
||||
want: map[string]interface{}{
|
||||
"a": map[string]interface{}{},
|
||||
"b": []interface{}{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
input := yaml.JSONObjectToYAMLObject(tt.input)
|
||||
SortMapSlice(input)
|
||||
|
||||
expect, err := goyaml.Marshal(input)
|
||||
if err != nil {
|
||||
t.Fatalf("error marshalling output: %v", err)
|
||||
}
|
||||
actual, err := goyaml.Marshal(tt.want)
|
||||
if err != nil {
|
||||
t.Fatalf("error marshalling want: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(expect, actual) {
|
||||
t.Errorf("SortMapSlice() = %s, want %s", expect, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue