Skip to content

Commit

Permalink
Merge pull request #49 from vshn/feat/allow-redis-upgrade
Browse files Browse the repository at this point in the history
Allow upgrading redis to newer versions
  • Loading branch information
TheBigLee authored Aug 2, 2023
2 parents 30817f0 + 6a0db3e commit 2c3cd69
Show file tree
Hide file tree
Showing 3 changed files with 294 additions and 10 deletions.
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

0 comments on commit 2c3cd69

Please sign in to comment.