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 (
|
require (
|
||||||
github.com/fluxcd/pkg/apis/kustomize v1.7.0
|
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/apiextensions-apiserver v0.31.3
|
||||||
k8s.io/apimachinery v0.31.3
|
k8s.io/apimachinery v0.31.3
|
||||||
sigs.k8s.io/controller-runtime v0.19.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/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 h1:4N23LccihQ3Ose/1FYZGwhrFSh63C9uOVFhwmSInOfQ=
|
||||||
github.com/fluxcd/pkg/apis/kustomize v1.7.0/go.mod h1:CqWLBcY2ZPW5f3k2sEypSfjIhz2hFs70PTTYIdKTMaY=
|
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.8.0 h1:wF7MJ3mu5ds9Y/exWU1yU0YyDb8s1VwwQnZYuMWli3c=
|
||||||
github.com/fluxcd/pkg/apis/meta v1.7.0/go.mod h1:OJGH7I//SNO6zcso80oBRuf5H8oU8etZDeTgCcH7qHo=
|
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 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
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,
|
// ValuesFrom holds references to resources containing Helm values for this HelmRelease,
|
||||||
// and information about how they should be merged.
|
// 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.
|
// Values holds the values for this Helm release.
|
||||||
// +optional
|
// +optional
|
||||||
|
|
|
||||||
|
|
@ -68,48 +68,3 @@ type CrossNamespaceSourceReference struct {
|
||||||
// +optional
|
// +optional
|
||||||
Namespace string `json:"namespace,omitempty"`
|
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 {
|
if in.ValuesFrom != nil {
|
||||||
in, out := &in.ValuesFrom, &out.ValuesFrom
|
in, out := &in.ValuesFrom, &out.ValuesFrom
|
||||||
*out = make([]ValuesReference, len(*in))
|
*out = make([]meta.ValuesReference, len(*in))
|
||||||
copy(*out, *in)
|
copy(*out, *in)
|
||||||
}
|
}
|
||||||
if in.Values != nil {
|
if in.Values != nil {
|
||||||
|
|
@ -717,18 +717,3 @@ func (in *UpgradeRemediation) DeepCopy() *UpgradeRemediation {
|
||||||
in.DeepCopyInto(out)
|
in.DeepCopyInto(out)
|
||||||
return 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>
|
<td>
|
||||||
<code>valuesFrom</code><br>
|
<code>valuesFrom</code><br>
|
||||||
<em>
|
<em>
|
||||||
<a href="#helm.toolkit.fluxcd.io/v2.ValuesReference">
|
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#ValuesReference">
|
||||||
[]ValuesReference
|
[]github.com/fluxcd/pkg/apis/meta.ValuesReference
|
||||||
</a>
|
</a>
|
||||||
</em>
|
</em>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -1360,8 +1360,8 @@ Uninstall
|
||||||
<td>
|
<td>
|
||||||
<code>valuesFrom</code><br>
|
<code>valuesFrom</code><br>
|
||||||
<em>
|
<em>
|
||||||
<a href="#helm.toolkit.fluxcd.io/v2.ValuesReference">
|
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#ValuesReference">
|
||||||
[]ValuesReference
|
[]github.com/fluxcd/pkg/apis/meta.ValuesReference
|
||||||
</a>
|
</a>
|
||||||
</em>
|
</em>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -2887,92 +2887,6 @@ RemediationStrategy
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="admonition note">
|
||||||
<p class="last">This page was automatically generated with <code>gen-crd-api-reference-docs</code></p>
|
<p class="last">This page was automatically generated with <code>gen-crd-api-reference-docs</code></p>
|
||||||
</div>
|
</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/acl v0.4.0
|
||||||
github.com/fluxcd/pkg/apis/event v0.11.0
|
github.com/fluxcd/pkg/apis/event v0.11.0
|
||||||
github.com/fluxcd/pkg/apis/kustomize v1.7.0
|
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
|
||||||
github.com/fluxcd/pkg/runtime v0.50.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/ssa v0.41.1
|
||||||
github.com/fluxcd/pkg/testserver v0.8.0
|
github.com/fluxcd/pkg/testserver v0.8.0
|
||||||
github.com/fluxcd/source-controller/api v1.4.1
|
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/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 h1:4N23LccihQ3Ose/1FYZGwhrFSh63C9uOVFhwmSInOfQ=
|
||||||
github.com/fluxcd/pkg/apis/kustomize v1.7.0/go.mod h1:CqWLBcY2ZPW5f3k2sEypSfjIhz2hFs70PTTYIdKTMaY=
|
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.8.0 h1:wF7MJ3mu5ds9Y/exWU1yU0YyDb8s1VwwQnZYuMWli3c=
|
||||||
github.com/fluxcd/pkg/apis/meta v1.7.0/go.mod h1:OJGH7I//SNO6zcso80oBRuf5H8oU8etZDeTgCcH7qHo=
|
github.com/fluxcd/pkg/apis/meta v1.8.0/go.mod h1:OJGH7I//SNO6zcso80oBRuf5H8oU8etZDeTgCcH7qHo=
|
||||||
github.com/fluxcd/pkg/runtime v0.50.0 h1:FKJQaOFv8SKp/t7yRE0EkHxA4RIr650SGTLJa1HY3AU=
|
github.com/fluxcd/pkg/chartutil v1.0.0 h1:Hj5mPiUp/nanZPVK7Ur0TDN4BCMhuoxKjvAmBbnX7DE=
|
||||||
github.com/fluxcd/pkg/runtime v0.50.0/go.mod h1:NEjX8/1DL8B/dsjH1/FD9PjCLPhgdvsffSvzuFrgjys=
|
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 h1:VW87zsLYAKUvCxJhuEH7VzxVh3SxaU+PyApCT6gKjTk=
|
||||||
github.com/fluxcd/pkg/ssa v0.41.1/go.mod h1:7cbyLHqFd5FpcKvhxbHG3DkMm3cZteW45Mi78B0hg8g=
|
github.com/fluxcd/pkg/ssa v0.41.1/go.mod h1:7cbyLHqFd5FpcKvhxbHG3DkMm3cZteW45Mi78B0hg8g=
|
||||||
github.com/fluxcd/pkg/testserver v0.8.0 h1:ndlCjNpIueEmsLbyg97Dbkq/0Mfzxn4Kq4HSPEb71V8=
|
github.com/fluxcd/pkg/testserver v0.8.0 h1:ndlCjNpIueEmsLbyg97Dbkq/0Mfzxn4Kq4HSPEb71V8=
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"helm.sh/helm/v3/pkg/chartutil"
|
"helm.sh/helm/v3/pkg/chartutil"
|
||||||
|
|
||||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||||
intchartutil "github.com/fluxcd/helm-controller/internal/chartutil"
|
intchartutil "github.com/fluxcd/pkg/chartutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ import (
|
||||||
helmdriver "helm.sh/helm/v3/pkg/storage/driver"
|
helmdriver "helm.sh/helm/v3/pkg/storage/driver"
|
||||||
|
|
||||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
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/helm-controller/internal/release"
|
||||||
|
"github.com/fluxcd/pkg/chartutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||||
sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2"
|
sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||||
|
|
||||||
|
"github.com/fluxcd/pkg/chartutil"
|
||||||
|
|
||||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||||
intacl "github.com/fluxcd/helm-controller/internal/acl"
|
intacl "github.com/fluxcd/helm-controller/internal/acl"
|
||||||
"github.com/fluxcd/helm-controller/internal/action"
|
"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/digest"
|
||||||
interrors "github.com/fluxcd/helm-controller/internal/errors"
|
interrors "github.com/fluxcd/helm-controller/internal/errors"
|
||||||
"github.com/fluxcd/helm-controller/internal/features"
|
"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.
|
// 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 {
|
if err != nil {
|
||||||
conditions.MarkFalse(obj, meta.ReadyCondition, "ValuesError", "%s", err)
|
conditions.MarkFalse(obj, meta.ReadyCondition, "ValuesError", "%s", err)
|
||||||
r.Eventf(obj, corev1.EventTypeWarning, "ValuesError", err.Error())
|
r.Eventf(obj, corev1.EventTypeWarning, "ValuesError", err.Error())
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ import (
|
||||||
aclv1 "github.com/fluxcd/pkg/apis/acl"
|
aclv1 "github.com/fluxcd/pkg/apis/acl"
|
||||||
"github.com/fluxcd/pkg/apis/kustomize"
|
"github.com/fluxcd/pkg/apis/kustomize"
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
"github.com/fluxcd/pkg/chartutil"
|
||||||
"github.com/fluxcd/pkg/runtime/conditions"
|
"github.com/fluxcd/pkg/runtime/conditions"
|
||||||
feathelper "github.com/fluxcd/pkg/runtime/features"
|
feathelper "github.com/fluxcd/pkg/runtime/features"
|
||||||
"github.com/fluxcd/pkg/runtime/patch"
|
"github.com/fluxcd/pkg/runtime/patch"
|
||||||
|
|
@ -58,7 +59,6 @@ import (
|
||||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||||
intacl "github.com/fluxcd/helm-controller/internal/acl"
|
intacl "github.com/fluxcd/helm-controller/internal/acl"
|
||||||
"github.com/fluxcd/helm-controller/internal/action"
|
"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/features"
|
||||||
"github.com/fluxcd/helm-controller/internal/kube"
|
"github.com/fluxcd/helm-controller/internal/kube"
|
||||||
"github.com/fluxcd/helm-controller/internal/postrender"
|
"github.com/fluxcd/helm-controller/internal/postrender"
|
||||||
|
|
@ -331,7 +331,7 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) {
|
||||||
Namespace: "mock",
|
Namespace: "mock",
|
||||||
},
|
},
|
||||||
Spec: v2.HelmReleaseSpec{
|
Spec: v2.HelmReleaseSpec{
|
||||||
ValuesFrom: []v2.ValuesReference{
|
ValuesFrom: []meta.ValuesReference{
|
||||||
{
|
{
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
Name: "missing",
|
Name: "missing",
|
||||||
|
|
@ -1537,7 +1537,7 @@ func TestHelmReleaseReconciler_reconcileReleaseFromOCIRepositorySource(t *testin
|
||||||
Name: "ocirepo",
|
Name: "ocirepo",
|
||||||
Namespace: "mock",
|
Namespace: "mock",
|
||||||
},
|
},
|
||||||
ValuesFrom: []v2.ValuesReference{
|
ValuesFrom: []meta.ValuesReference{
|
||||||
{
|
{
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
Name: "missing",
|
Name: "missing",
|
||||||
|
|
@ -3526,12 +3526,12 @@ func Test_waitForHistoryCacheSync(t *testing.T) {
|
||||||
func TestValuesReferenceValidation(t *testing.T) {
|
func TestValuesReferenceValidation(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
references []v2.ValuesReference
|
references []meta.ValuesReference
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "valid ValuesKey",
|
name: "valid ValuesKey",
|
||||||
references: []v2.ValuesReference{
|
references: []meta.ValuesReference{
|
||||||
{
|
{
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
Name: "values",
|
Name: "values",
|
||||||
|
|
@ -3542,7 +3542,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid ValuesKey: empty",
|
name: "valid ValuesKey: empty",
|
||||||
references: []v2.ValuesReference{
|
references: []meta.ValuesReference{
|
||||||
{
|
{
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
Name: "values",
|
Name: "values",
|
||||||
|
|
@ -3553,7 +3553,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid ValuesKey: long",
|
name: "valid ValuesKey: long",
|
||||||
references: []v2.ValuesReference{
|
references: []meta.ValuesReference{
|
||||||
{
|
{
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
Name: "values",
|
Name: "values",
|
||||||
|
|
@ -3564,7 +3564,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid ValuesKey",
|
name: "invalid ValuesKey",
|
||||||
references: []v2.ValuesReference{
|
references: []meta.ValuesReference{
|
||||||
{
|
{
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
Name: "values",
|
Name: "values",
|
||||||
|
|
@ -3575,7 +3575,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid ValuesKey: too long",
|
name: "invalid ValuesKey: too long",
|
||||||
references: []v2.ValuesReference{
|
references: []meta.ValuesReference{
|
||||||
{
|
{
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
Name: "values",
|
Name: "values",
|
||||||
|
|
@ -3586,7 +3586,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid target path: empty",
|
name: "valid target path: empty",
|
||||||
references: []v2.ValuesReference{
|
references: []meta.ValuesReference{
|
||||||
{
|
{
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
Name: "values",
|
Name: "values",
|
||||||
|
|
@ -3597,7 +3597,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid target path",
|
name: "valid target path",
|
||||||
references: []v2.ValuesReference{
|
references: []meta.ValuesReference{
|
||||||
{
|
{
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
Name: "values",
|
Name: "values",
|
||||||
|
|
@ -3608,7 +3608,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid target path: long",
|
name: "valid target path: long",
|
||||||
references: []v2.ValuesReference{
|
references: []meta.ValuesReference{
|
||||||
{
|
{
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
Name: "values",
|
Name: "values",
|
||||||
|
|
@ -3619,7 +3619,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid target path: too long",
|
name: "invalid target path: too long",
|
||||||
references: []v2.ValuesReference{
|
references: []meta.ValuesReference{
|
||||||
{
|
{
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
Name: "values",
|
Name: "values",
|
||||||
|
|
@ -3630,7 +3630,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid target path: opened index",
|
name: "invalid target path: opened index",
|
||||||
references: []v2.ValuesReference{
|
references: []meta.ValuesReference{
|
||||||
{
|
{
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
Name: "values",
|
Name: "values",
|
||||||
|
|
@ -3642,7 +3642,7 @@ func TestValuesReferenceValidation(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid target path: incorrect index syntax",
|
name: "invalid target path: incorrect index syntax",
|
||||||
references: []v2.ValuesReference{
|
references: []meta.ValuesReference{
|
||||||
{
|
{
|
||||||
Kind: "Secret",
|
Kind: "Secret",
|
||||||
Name: "values",
|
Name: "values",
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@ import (
|
||||||
|
|
||||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||||
"github.com/fluxcd/helm-controller/internal/action"
|
"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/digest"
|
||||||
|
"github.com/fluxcd/pkg/chartutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Install is an ActionReconciler which attempts to install a Helm release
|
// Install is an ActionReconciler which attempts to install a Helm release
|
||||||
|
|
|
||||||
|
|
@ -40,11 +40,11 @@ import (
|
||||||
|
|
||||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||||
"github.com/fluxcd/helm-controller/internal/action"
|
"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/digest"
|
||||||
"github.com/fluxcd/helm-controller/internal/release"
|
"github.com/fluxcd/helm-controller/internal/release"
|
||||||
"github.com/fluxcd/helm-controller/internal/storage"
|
"github.com/fluxcd/helm-controller/internal/storage"
|
||||||
"github.com/fluxcd/helm-controller/internal/testutil"
|
"github.com/fluxcd/helm-controller/internal/testutil"
|
||||||
|
"github.com/fluxcd/pkg/chartutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInstall_Reconcile(t *testing.T) {
|
func TestInstall_Reconcile(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,10 @@ import (
|
||||||
|
|
||||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||||
"github.com/fluxcd/helm-controller/internal/action"
|
"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/digest"
|
||||||
"github.com/fluxcd/helm-controller/internal/release"
|
"github.com/fluxcd/helm-controller/internal/release"
|
||||||
"github.com/fluxcd/helm-controller/internal/storage"
|
"github.com/fluxcd/helm-controller/internal/storage"
|
||||||
|
"github.com/fluxcd/pkg/chartutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RollbackRemediation is an ActionReconciler which attempts to roll back
|
// RollbackRemediation is an ActionReconciler which attempts to roll back
|
||||||
|
|
|
||||||
|
|
@ -39,11 +39,11 @@ import (
|
||||||
|
|
||||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||||
"github.com/fluxcd/helm-controller/internal/action"
|
"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/digest"
|
||||||
"github.com/fluxcd/helm-controller/internal/release"
|
"github.com/fluxcd/helm-controller/internal/release"
|
||||||
"github.com/fluxcd/helm-controller/internal/storage"
|
"github.com/fluxcd/helm-controller/internal/storage"
|
||||||
"github.com/fluxcd/helm-controller/internal/testutil"
|
"github.com/fluxcd/helm-controller/internal/testutil"
|
||||||
|
"github.com/fluxcd/pkg/chartutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRollbackRemediation_Reconcile(t *testing.T) {
|
func TestRollbackRemediation_Reconcile(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,10 @@ import (
|
||||||
|
|
||||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||||
"github.com/fluxcd/helm-controller/internal/action"
|
"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/digest"
|
||||||
"github.com/fluxcd/helm-controller/internal/release"
|
"github.com/fluxcd/helm-controller/internal/release"
|
||||||
"github.com/fluxcd/helm-controller/internal/testutil"
|
"github.com/fluxcd/helm-controller/internal/testutil"
|
||||||
|
"github.com/fluxcd/pkg/chartutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// testHookFixtures is a list of release.Hook in every possible LastRun state.
|
// 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"
|
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||||
"github.com/fluxcd/helm-controller/internal/action"
|
"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/digest"
|
||||||
"github.com/fluxcd/helm-controller/internal/release"
|
"github.com/fluxcd/helm-controller/internal/release"
|
||||||
"github.com/fluxcd/helm-controller/internal/storage"
|
"github.com/fluxcd/helm-controller/internal/storage"
|
||||||
"github.com/fluxcd/helm-controller/internal/testutil"
|
"github.com/fluxcd/helm-controller/internal/testutil"
|
||||||
|
"github.com/fluxcd/pkg/chartutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUninstallRemediation_Reconcile(t *testing.T) {
|
func TestUninstallRemediation_Reconcile(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,11 @@ import (
|
||||||
|
|
||||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||||
"github.com/fluxcd/helm-controller/internal/action"
|
"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/digest"
|
||||||
"github.com/fluxcd/helm-controller/internal/release"
|
"github.com/fluxcd/helm-controller/internal/release"
|
||||||
"github.com/fluxcd/helm-controller/internal/storage"
|
"github.com/fluxcd/helm-controller/internal/storage"
|
||||||
"github.com/fluxcd/helm-controller/internal/testutil"
|
"github.com/fluxcd/helm-controller/internal/testutil"
|
||||||
|
"github.com/fluxcd/pkg/chartutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUninstall_Reconcile(t *testing.T) {
|
func TestUninstall_Reconcile(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,11 @@ import (
|
||||||
|
|
||||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||||
"github.com/fluxcd/helm-controller/internal/action"
|
"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/digest"
|
||||||
"github.com/fluxcd/helm-controller/internal/release"
|
"github.com/fluxcd/helm-controller/internal/release"
|
||||||
"github.com/fluxcd/helm-controller/internal/storage"
|
"github.com/fluxcd/helm-controller/internal/storage"
|
||||||
"github.com/fluxcd/helm-controller/internal/testutil"
|
"github.com/fluxcd/helm-controller/internal/testutil"
|
||||||
|
"github.com/fluxcd/pkg/chartutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUnlock_Reconcile(t *testing.T) {
|
func TestUnlock_Reconcile(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@ import (
|
||||||
|
|
||||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||||
"github.com/fluxcd/helm-controller/internal/action"
|
"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/digest"
|
||||||
|
"github.com/fluxcd/pkg/chartutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Upgrade is an ActionReconciler which attempts to upgrade a Helm release
|
// Upgrade is an ActionReconciler which attempts to upgrade a Helm release
|
||||||
|
|
|
||||||
|
|
@ -40,11 +40,11 @@ import (
|
||||||
|
|
||||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
v2 "github.com/fluxcd/helm-controller/api/v2"
|
||||||
"github.com/fluxcd/helm-controller/internal/action"
|
"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/digest"
|
||||||
"github.com/fluxcd/helm-controller/internal/release"
|
"github.com/fluxcd/helm-controller/internal/release"
|
||||||
"github.com/fluxcd/helm-controller/internal/storage"
|
"github.com/fluxcd/helm-controller/internal/storage"
|
||||||
"github.com/fluxcd/helm-controller/internal/testutil"
|
"github.com/fluxcd/helm-controller/internal/testutil"
|
||||||
|
"github.com/fluxcd/pkg/chartutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUpgrade_Reconcile(t *testing.T) {
|
func TestUpgrade_Reconcile(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
v2 "github.com/fluxcd/helm-controller/api/v2"
|
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/helm-controller/internal/digest"
|
||||||
|
"github.com/fluxcd/pkg/chartutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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