cli: helm: separate user input from static loading

Because values in the charts might change in the future and
some values (like the image) are part of a valid upgrade we
need to load all values for an upgrade.
However, during upgrades we don't want to reapply user
input like the masterSecret. Therefore this patch splits the
application of user input and the static loading of chart values.
This commit is contained in:
Otto Bittner 2023-02-14 16:41:19 +01:00
parent 69a384d978
commit 1728633646
3 changed files with 75 additions and 42 deletions

View file

@ -171,7 +171,7 @@ func (c *Client) upgradeRelease(
return nil return nil
} }
// prepareCertManagerValues returns a values map as required for helm-upgrade. // prepareValues returns a values map as required for helm-upgrade.
// It imitates the behaviour of helm's reuse-values flag by fetching the current values from the cluster // It imitates the behaviour of helm's reuse-values flag by fetching the current values from the cluster
// and merging the fetched values with the locally found values. // and merging the fetched values with the locally found values.
// This is done to ensure that new values (from upgrades of the local files) end up in the cluster. // This is done to ensure that new values (from upgrades of the local files) end up in the cluster.

View file

@ -103,10 +103,11 @@ func AvailableServiceVersions() (string, error) {
// Load the embedded helm charts. // Load the embedded helm charts.
func (i *ChartLoader) Load(config *config.Config, conformanceMode bool, masterSecret, salt []byte) ([]byte, error) { func (i *ChartLoader) Load(config *config.Config, conformanceMode bool, masterSecret, salt []byte) ([]byte, error) {
ciliumRelease, err := i.loadCilium(config.GetProvider(), conformanceMode) ciliumRelease, err := i.loadCilium(config.GetProvider())
if err != nil { if err != nil {
return nil, fmt.Errorf("loading cilium: %w", err) return nil, fmt.Errorf("loading cilium: %w", err)
} }
extendCiliumValues(ciliumRelease.Values, conformanceMode)
certManagerRelease, err := i.loadCertManager() certManagerRelease, err := i.loadCertManager()
if err != nil { if err != nil {
@ -118,10 +119,14 @@ func (i *ChartLoader) Load(config *config.Config, conformanceMode bool, masterSe
return nil, fmt.Errorf("loading operators: %w", err) return nil, fmt.Errorf("loading operators: %w", err)
} }
conServicesRelease, err := i.loadConstellationServices(config, masterSecret, salt) conServicesRelease, err := i.loadConstellationServices(config.GetProvider())
if err != nil { if err != nil {
return nil, fmt.Errorf("loading constellation-services: %w", err) return nil, fmt.Errorf("loading constellation-services: %w", err)
} }
if err := extendConstellationServicesValues(conServicesRelease.Values, config, masterSecret, salt); err != nil {
return nil, fmt.Errorf("extending constellation-services values: %w", err)
}
releases := helm.Releases{Cilium: ciliumRelease, CertManager: certManagerRelease, Operators: operatorRelease, ConstellationServices: conServicesRelease} releases := helm.Releases{Cilium: ciliumRelease, CertManager: certManagerRelease, Operators: operatorRelease, ConstellationServices: conServicesRelease}
rel, err := json.Marshal(releases) rel, err := json.Marshal(releases)
@ -131,12 +136,13 @@ func (i *ChartLoader) Load(config *config.Config, conformanceMode bool, masterSe
return rel, nil return rel, nil
} }
func (i *ChartLoader) loadCilium(csp cloudprovider.Provider, conformanceMode bool) (helm.Release, error) { // loadCilium prepares a helm release for use in a helm install action.
func (i *ChartLoader) loadCilium(csp cloudprovider.Provider) (helm.Release, error) {
chart, err := loadChartsDir(helmFS, ciliumPath) chart, err := loadChartsDir(helmFS, ciliumPath)
if err != nil { if err != nil {
return helm.Release{}, fmt.Errorf("loading cilium chart: %w", err) return helm.Release{}, fmt.Errorf("loading cilium chart: %w", err)
} }
values, err := i.loadCiliumValues(csp, conformanceMode) values, err := i.loadCiliumValues(csp)
if err != nil { if err != nil {
return helm.Release{}, err return helm.Release{}, err
} }
@ -149,9 +155,9 @@ func (i *ChartLoader) loadCilium(csp cloudprovider.Provider, conformanceMode boo
return helm.Release{Chart: chartRaw, Values: values, ReleaseName: ciliumReleaseName, Wait: false}, nil return helm.Release{Chart: chartRaw, Values: values, ReleaseName: ciliumReleaseName, Wait: false}, nil
} }
// loadCiliumHelper is used to separate the marshalling step from the loading step. // loadCiliumValues is used to separate the marshalling step from the loading step.
// This reduces the time unit tests take to execute. // This reduces the time unit tests take to execute.
func (i *ChartLoader) loadCiliumValues(csp cloudprovider.Provider, conformanceMode bool) (map[string]any, error) { func (i *ChartLoader) loadCiliumValues(csp cloudprovider.Provider) (map[string]any, error) {
var values map[string]any var values map[string]any
switch csp { switch csp {
case cloudprovider.AWS: case cloudprovider.AWS:
@ -165,16 +171,24 @@ func (i *ChartLoader) loadCiliumValues(csp cloudprovider.Provider, conformanceMo
default: default:
return nil, fmt.Errorf("unknown csp: %s", csp) return nil, fmt.Errorf("unknown csp: %s", csp)
} }
if conformanceMode {
values["kubeProxyReplacementHealthzBindAddr"] = "" return values, nil
values["kubeProxyReplacement"] = "partial"
values["sessionAffinity"] = true
values["cni"] = map[string]any{
"chainingMode": "portmap",
} }
// extendCiliumValues extends the given values map by some values depending on user input.
// This extra step of separating the application of user input is necessary since service upgrades should
// reuse user input from the init step. However, we can't rely on reuse-values, because
// during upgrades we all values need to be set locally as they might have changed.
// Also, the charts are not rendered correctly without all of these values.
func extendCiliumValues(in map[string]any, conformanceMode bool) {
if conformanceMode {
in["kubeProxyReplacementHealthzBindAddr"] = ""
in["kubeProxyReplacement"] = "partial"
in["sessionAffinity"] = true
in["cni"] = map[string]any{
"chainingMode": "portmap",
}
} }
return values, nil
} }
func (i *ChartLoader) loadCertManager() (helm.Release, error) { func (i *ChartLoader) loadCertManager() (helm.Release, error) {
@ -344,14 +358,13 @@ func (i *ChartLoader) loadOperatorsValues(csp cloudprovider.Provider) (map[strin
return values, nil return values, nil
} }
// loadConstellationServices loads the constellation-services chart from the embed.FS, // loadConstellationServices prepares a helm release for use in a helm install action.
// marshals it into a helm-package .tgz and sets the values that can be set in the CLI. func (i *ChartLoader) loadConstellationServices(csp cloudprovider.Provider) (helm.Release, error) {
func (i *ChartLoader) loadConstellationServices(config *config.Config, masterSecret, salt []byte) (helm.Release, error) {
chart, err := loadChartsDir(helmFS, conServicesPath) chart, err := loadChartsDir(helmFS, conServicesPath)
if err != nil { if err != nil {
return helm.Release{}, fmt.Errorf("loading constellation-services chart: %w", err) return helm.Release{}, fmt.Errorf("loading constellation-services chart: %w", err)
} }
values, err := i.loadConstellationServicesValues(config, masterSecret, salt) values, err := i.loadConstellationServicesValues(csp)
if err != nil { if err != nil {
return helm.Release{}, err return helm.Release{}, err
} }
@ -366,8 +379,7 @@ func (i *ChartLoader) loadConstellationServices(config *config.Config, masterSec
// loadConstellationServicesHelper is used to separate the marshalling step from the loading step. // loadConstellationServicesHelper is used to separate the marshalling step from the loading step.
// This reduces the time unit tests take to execute. // This reduces the time unit tests take to execute.
func (i *ChartLoader) loadConstellationServicesValues(config *config.Config, masterSecret, salt []byte) (map[string]any, error) { func (i *ChartLoader) loadConstellationServicesValues(csp cloudprovider.Provider) (map[string]any, error) {
csp := config.GetProvider()
values := map[string]any{ values := map[string]any{
"global": map[string]any{ "global": map[string]any{
"keyServicePort": constants.KeyServicePort, "keyServicePort": constants.KeyServicePort,
@ -378,8 +390,6 @@ func (i *ChartLoader) loadConstellationServicesValues(config *config.Config, mas
}, },
"key-service": map[string]any{ "key-service": map[string]any{
"image": i.keyServiceImage, "image": i.keyServiceImage,
"masterSecret": base64.StdEncoding.EncodeToString(masterSecret),
"salt": base64.StdEncoding.EncodeToString(salt),
"saltKeyName": constants.ConstellationSaltKey, "saltKeyName": constants.ConstellationSaltKey,
"masterSecretKeyName": constants.ConstellationMasterSecretKey, "masterSecretKeyName": constants.ConstellationMasterSecretKey,
"masterSecretName": constants.ConstellationMasterSecretStoreName, "masterSecretName": constants.ConstellationMasterSecretStoreName,
@ -410,18 +420,6 @@ func (i *ChartLoader) loadConstellationServicesValues(config *config.Config, mas
switch csp { switch csp {
case cloudprovider.Azure: case cloudprovider.Azure:
joinServiceVals, ok := values["join-service"].(map[string]any)
if !ok {
return nil, errors.New("invalid join-service values")
}
joinServiceVals["enforceIdKeyDigest"] = config.EnforcesIDKeyDigest()
marshalledDigests, err := json.Marshal(config.IDKeyDigests())
if err != nil {
return nil, fmt.Errorf("marshalling id key digests: %w", err)
}
joinServiceVals["idkeydigests"] = string(marshalledDigests)
ccmVals, ok := values["ccm"].(map[string]any) ccmVals, ok := values["ccm"].(map[string]any)
if !ok { if !ok {
return nil, errors.New("invalid ccm values") return nil, errors.New("invalid ccm values")
@ -434,10 +432,6 @@ func (i *ChartLoader) loadConstellationServicesValues(config *config.Config, mas
"image": i.cnmImage, "image": i.cnmImage,
} }
values["azure"] = map[string]any{
"deployCSIDriver": config.DeployCSIDriver(),
}
values["tags"] = map[string]any{ values["tags"] = map[string]any{
"Azure": true, "Azure": true,
} }
@ -451,10 +445,6 @@ func (i *ChartLoader) loadConstellationServicesValues(config *config.Config, mas
"image": i.ccmImage, "image": i.ccmImage,
} }
values["gcp"] = map[string]any{
"deployCSIDriver": config.DeployCSIDriver(),
}
values["tags"] = map[string]any{ values["tags"] = map[string]any{
"GCP": true, "GCP": true,
} }
@ -480,6 +470,47 @@ func (i *ChartLoader) loadConstellationServicesValues(config *config.Config, mas
return values, nil return values, nil
} }
// extendConstellationServicesValues extends the given values map by some values depending on user input.
// This extra step of separating the application of user input is necessary since service upgrades should
// reuse user input from the init step. However, we can't rely on reuse-values, because
// during upgrades all values need to be set locally as they might have changed.
// Also, the charts are not rendered correctly without all of these values.
func extendConstellationServicesValues(in map[string]any, config *config.Config, masterSecret, salt []byte) error {
keyServiceValues, ok := in["key-service"].(map[string]any)
if !ok {
return errors.New("missing 'key-service' key")
}
keyServiceValues["masterSecret"] = base64.StdEncoding.EncodeToString(masterSecret)
keyServiceValues["salt"] = base64.StdEncoding.EncodeToString(salt)
csp := config.GetProvider()
switch csp {
case cloudprovider.Azure:
joinServiceVals, ok := in["join-service"].(map[string]any)
if !ok {
return errors.New("invalid join-service values")
}
joinServiceVals["enforceIdKeyDigest"] = config.EnforcesIDKeyDigest()
marshalledDigests, err := json.Marshal(config.IDKeyDigests())
if err != nil {
return fmt.Errorf("marshalling id key digests: %w", err)
}
joinServiceVals["idkeydigests"] = string(marshalledDigests)
in["azure"] = map[string]any{
"deployCSIDriver": config.DeployCSIDriver(),
}
case cloudprovider.GCP:
in["gcp"] = map[string]any{
"deployCSIDriver": config.DeployCSIDriver(),
}
}
return nil
}
// marshalChart takes a Chart object, packages it to a temporary file and returns the content of that file. // marshalChart takes a Chart object, packages it to a temporary file and returns the content of that file.
// We currently need to take this approach of marshaling as dependencies are not marshaled correctly with json.Marshal. // We currently need to take this approach of marshaling as dependencies are not marshaled correctly with json.Marshal.
// This stems from the fact that chart.Chart does not export the dependencies property. // This stems from the fact that chart.Chart does not export the dependencies property.

View file

@ -100,7 +100,9 @@ func TestConstellationServices(t *testing.T) {
} }
chart, err := loadChartsDir(helmFS, conServicesPath) chart, err := loadChartsDir(helmFS, conServicesPath)
require.NoError(err) require.NoError(err)
values, err := chartLoader.loadConstellationServicesValues(tc.config, []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) values, err := chartLoader.loadConstellationServicesValues(tc.config.GetProvider())
require.NoError(err)
err = extendConstellationServicesValues(values, tc.config, []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
require.NoError(err) require.NoError(err)
options := chartutil.ReleaseOptions{ options := chartutil.ReleaseOptions{