Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow upgrading redis to newer versions #49

Merged
merged 2 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 24 additions & 10 deletions pkg/comp-functions/functions/vshnredis/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"

"github.com/blang/semver/v4"
"github.com/crossplane-contrib/provider-helm/apis/release/v1beta1"
vshnv1 "github.com/vshn/appcat/apis/vshn/v1"
"github.com/vshn/appcat/pkg/comp-functions/runtime"
Expand Down Expand Up @@ -86,7 +87,7 @@ func updateRelease(ctx context.Context, comp *vshnv1.VSHNRedis, desired *v1beta1
}

l.V(1).Info("Setting release version")
err = setReleaseVersion(comp, values, observedValues)
err = setReleaseVersion(ctx, comp, values, observedValues)
if err != nil {
return nil, fmt.Errorf("cannot set redis version for release %s: %v", releaseName, err)
}
Expand Down Expand Up @@ -194,18 +195,31 @@ func getReleaseValues(r *v1beta1.Release) (map[string]interface{}, error) {
}

// setReleaseVersion sets the version from the claim if it's a new instance otherwise it is managed by maintenance function
func setReleaseVersion(comp *vshnv1.VSHNRedis, values map[string]interface{}, observed map[string]interface{}) error {
var err error
func setReleaseVersion(ctx context.Context, comp *vshnv1.VSHNRedis, values map[string]interface{}, observed map[string]interface{}) error {
l := controllerruntime.LoggerFrom(ctx)

tag, _, err := unstructured.NestedString(observed, "image", "tag")
if err != nil {
return fmt.Errorf("cannot get image tag from values in release: %v", err)
}
if tag != "" {
// In case the tag is set, keep the observed version
err = unstructured.SetNestedField(values, tag, "image", "tag")
} else {
// In case the tag is not set then this is a new Release therefore we need to set the version from the claim
err = unstructured.SetNestedField(values, comp.Spec.Parameters.Service.Version, "image", "tag")

desiredVersion, err := semver.ParseTolerant(comp.Spec.Parameters.Service.Version)
if err != nil {
l.Info("failed to parse desired redis version", "version", comp.Spec.Parameters.Service.Version)
return fmt.Errorf("invalid redis version %q", comp.Spec.Parameters.Service.Version)
}

observedVersion, err := semver.ParseTolerant(tag)
if err != nil {
l.Info("failed to parse observed redis version", "version", tag)
// If the observed version is not parsable, e.g. if it's empty, update to the desired version
return unstructured.SetNestedField(values, comp.Spec.Parameters.Service.Version, "image", "tag")
}

if observedVersion.GTE(desiredVersion) {
// In case the overved tag is valid and greater than the desired version, keep the observed version
return unstructured.SetNestedField(values, tag, "image", "tag")
}
return err
// In case the observed tag is smaller than the desired version, then set the version from the claim
return unstructured.SetNestedField(values, comp.Spec.Parameters.Service.Version, "image", "tag")
}
115 changes: 115 additions & 0 deletions pkg/comp-functions/functions/vshnredis/release_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package vshnredis

import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"text/template"

xhelm "github.com/crossplane-contrib/provider-helm/apis/release/v1beta1"
crossfnv1alpha1 "github.com/crossplane/crossplane/apis/apiextensions/fn/io/v1alpha1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
vshnv1 "github.com/vshn/appcat/apis/vshn/v1"
"github.com/vshn/appcat/pkg/comp-functions/functions/commontest"
"github.com/vshn/appcat/pkg/comp-functions/runtime"
Expand Down Expand Up @@ -88,3 +95,111 @@ func getRedisReleaseComp(t *testing.T, file string) (*runtime.Runtime, *vshnv1.V

return iof, comp
}

func TestAllowVersionUpgrade(t *testing.T) {

tcs := map[string]struct {
CompositeVersion string
ObservedVersion string
Desired string
Fail bool
}{
"newMinor": {
CompositeVersion: "6.2",
ObservedVersion: "6.0.2",
Desired: "6.2",
},
"newMajor": {
CompositeVersion: "7.0",
ObservedVersion: "6.2.2",
Desired: "7.0",
},
"sameMinior": {
CompositeVersion: "6.2",
ObservedVersion: "6.2.2",
Desired: "6.2.2",
},
"sameVersion": {
CompositeVersion: "6.2",
ObservedVersion: "6.2",
Desired: "6.2",
},
"noObserved": {
CompositeVersion: "6.2",
ObservedVersion: "",
Desired: "6.2",
},
"downgrade": {
CompositeVersion: "6.2",
ObservedVersion: "7.0.2",
Desired: "7.0.2",
},
"invalidObserved": {
CompositeVersion: "6.2",
ObservedVersion: "randomTag",
Desired: "6.2",
},
"invalidDesired": {
CompositeVersion: "randomTag",
ObservedVersion: "7.0.3",
Desired: "7.0.3",
Fail: true,
},
"allInvalid": {
CompositeVersion: "randomTag",
ObservedVersion: "weirdTag",
Desired: "weirdTag",
Fail: true,
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {

ctx := context.TODO()
iof := loadRuntimeFromTemplate(t, fmt.Sprintf("vshnredis/release/03_version_update.yaml.tmpl"), tc)

res := ManageRelease(ctx, iof)
if tc.Fail {
assert.Equal(t, crossfnv1alpha1.SeverityFatal, res.Resolve().Severity)
return
}
assert.Equal(t, runtime.NewNormal(), res)

release := &xhelm.Release{}
assert.NoError(t, iof.Desired.Get(ctx, release, "release"))

valueMap := map[string]any{}
assert.NoError(t, yaml.Unmarshal(release.Spec.ForProvider.Values.Raw, &valueMap))
require.NotEmpty(t, valueMap["master"])
imageMap := valueMap["image"].(map[string]any)
assert.Equal(t, tc.Desired, imageMap["tag"])
})
}
}

func loadRuntimeFromTemplate(t assert.TestingT, file string, data any) *runtime.Runtime {
p, _ := filepath.Abs(".")
before, _, _ := strings.Cut(p, "pkg")
f, err := os.Open(before + "test/transforms/" + file)
assert.NoError(t, err)
b1, err := os.ReadFile(f.Name())
if err != nil {
assert.FailNow(t, "can't get example")
}

tmpl, err := template.New("test").Parse(string(b1))
if err != nil {
assert.FailNow(t, "can't load tempalte")
}

var buf bytes.Buffer
err = tmpl.Execute(&buf, data)
if err != nil {
assert.FailNow(t, "can't execute tempalte")
}

funcIO, err := runtime.NewRuntime(context.Background(), buf.Bytes())
assert.NoError(t, err)

return funcIO
}
155 changes: 155 additions & 0 deletions test/transforms/vshnredis/release/03_version_update.yaml.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
apiVersion: apiextensions.crossplane.io/v1alpha1
kind: FunctionIO
observed:
composite:
resource:
apiVersion: vshn.appcat.vshn.io/v1
kind: XVSHNRedis
metadata:
annotations:
creationTimestamp: "2023-03-21T16:52:31Z"
finalizers:
- composite.apiextensions.crossplane.io
generateName: redis-
generation: 13
labels:
appuio.io/organization: vshn
crossplane.io/claim-name: redis
crossplane.io/claim-namespace: unit-test
crossplane.io/composite: redis-gc9x4
name: redis-gc9x4
spec:
claimRef:
apiVersion: vshn.appcat.vshn.io/v1
kind: VSHNRedis
name: redis
namespace: unit-test
compositionRef:
name: vshnredis.vshn.appcat.vshn.io
compositionRevisionRef:
name: vshnredis.vshn.appcat.vshn.io-ce52f13
compositionUpdatePolicy: Automatic
parameters:
service:
version: "{{ .CompositeVersion }}"
resources:
- name: release
resource:
apiVersion: helm.crossplane.io/v1beta1
kind: Release
spec:
forProvider:
chart:
name: redis
repository: https://charts.bitnami.com/bitnami
values:
architecture: standalone
commonConfiguration: 'dummy'
fullnameOverride: redis
global:
imageRegistry: 'dummy'
image:
repository: bitnami/redis
tag: "{{ .ObservedVersion }}"
master:
extraVolumes: []
containerSecurityContext:
enabled: true
persistence:
size: '7Gi'
podSecurityContext:
enabled: true
resources:
limits:
cpu: '10m'
memory: '1Gi'
requests:
cpu: '10m'
memory: '1Gi'
networkPolicy:
allowExternal: false
enabled: true
ingressNSMatchLabels:
kubernetes.io/metadata.name: 'dummy'
tls:
authClients: true
autoGenerated: false
certCAFilename: ca.crt
certFilename: tls.crt
certKeyFilename: tls.key
enabled: true
existingSecret: tls-server-certificate
providerConfigRef:
name: helm
desired:
composite:
connectionDetails: null
resource:
apiVersion: vshn.appcat.vshn.io/v1
kind: XVSHNRedis
metadata:
creationTimestamp: "2023-03-21T16:52:31Z"
finalizers:
- composite.apiextensions.crossplane.io
generateName: redis-
generation: 13
labels:
appuio.io/organization: vshn
crossplane.io/claim-name: redis
crossplane.io/claim-namespace: unit-test
crossplane.io/composite: redis-gc9x4
name: redis-gc9x4
spec:
parameters:
service:
version: "{{ .CompositeVersion }}"
writeConnectionSecretToRef: {}
status: {}
resources:
- name: release
resource:
apiVersion: helm.crossplane.io/v1beta1
kind: Release
spec:
forProvider:
chart:
name: redis
repository: https://charts.bitnami.com/bitnami
values:
architecture: standalone
commonConfiguration: 'updated' # check that desired is applied
fullnameOverride: redis
global:
imageRegistry: 'dummy'
image:
repository: bitnami/redis
tag: '7.0'
master:
containerSecurityContext:
enabled: true
persistence:
size: '7Gi'
podSecurityContext:
enabled: true
resources:
limits:
cpu: '10m'
memory: '1Gi'
requests:
cpu: '10m'
memory: '1Gi'
networkPolicy:
allowExternal: false
enabled: true
ingressNSMatchLabels:
kubernetes.io/metadata.name: 'dummy'
tls:
authClients: true
autoGenerated: false
certCAFilename: ca.crt
certFilename: tls.crt
certKeyFilename: tls.key
enabled: true
existingSecret: tls-server-certificate
providerConfigRef:
name: helm