Skip to content

Commit 3cac8df

Browse files
danail-branekovgcapizziKieron Browne
authored andcommitted
Disallow rolling upgrades for appworkloads <= v0.7.1
Co-authored-by: Giuseppe Capizzi <gcapizzi@vmware.com> Co-authored-by: Kieron Browne <kbrowne@vmware.com>
1 parent 8dc4378 commit 3cac8df

File tree

8 files changed

+213
-1
lines changed

8 files changed

+213
-1
lines changed

api/repositories/deployment_repository.go

+38
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1"
1111
"code.cloudfoundry.org/korifi/controllers/controllers/shared"
1212
"code.cloudfoundry.org/korifi/tools/k8s"
13+
"code.cloudfoundry.org/korifi/version"
14+
"github.com/go-logr/logr"
1315

1416
"k8s.io/apimachinery/pkg/api/meta"
1517
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -101,6 +103,10 @@ func (r *DeploymentRepo) CreateDeployment(ctx context.Context, authInfo authoriz
101103
return DeploymentRecord{}, apierrors.FromK8sError(err, DeploymentResourceType)
102104
}
103105

106+
if err = ensureSupport(ctx, userClient, app); err != nil {
107+
return DeploymentRecord{}, err
108+
}
109+
104110
dropletGUID := app.Spec.CurrentDropletRef.Name
105111
if message.DropletGUID != "" {
106112
dropletGUID = message.DropletGUID
@@ -158,3 +164,35 @@ func appToDeploymentRecord(cfApp *korifiv1alpha1.CFApp) DeploymentRecord {
158164

159165
return deploymentRecord
160166
}
167+
168+
func ensureSupport(ctx context.Context, userClient client.Client, app *korifiv1alpha1.CFApp) error {
169+
log := logr.FromContextOrDiscard(ctx).WithName("repo.deployment.ensureSupport")
170+
171+
var appWorkloadsList korifiv1alpha1.AppWorkloadList
172+
err := userClient.List(ctx, &appWorkloadsList, client.InNamespace(app.Namespace), client.MatchingLabels{
173+
korifiv1alpha1.CFAppGUIDLabelKey: app.Name,
174+
})
175+
if err != nil {
176+
return apierrors.FromK8sError(err, DeploymentResourceType)
177+
}
178+
179+
checker := version.NewChecker("v0.7.1")
180+
for i := range appWorkloadsList.Items {
181+
appWorkload := appWorkloadsList.Items[i]
182+
newer, err := checker.ObjectIsNewer(&appWorkload)
183+
if newer {
184+
continue
185+
}
186+
187+
if err != nil {
188+
log.Info("failed comparining version of appWorkload",
189+
"ns", appWorkload.Namespace,
190+
"name", appWorkload.Name,
191+
"version", appWorkload.Annotations[version.KorifiCreationVersionKey],
192+
)
193+
}
194+
return apierrors.NewUnprocessableEntityError(nil, "App instances created with an older version of Korifi can't use the rolling strategy. Please restart/restage/re-push app before using the rolling strategy")
195+
}
196+
197+
return nil
198+
}

api/repositories/deployment_repository_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ import (
99
"code.cloudfoundry.org/korifi/controllers/controllers/shared"
1010
"code.cloudfoundry.org/korifi/tests/matchers"
1111
"code.cloudfoundry.org/korifi/tools/k8s"
12+
"code.cloudfoundry.org/korifi/version"
1213
"k8s.io/apimachinery/pkg/api/meta"
1314
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1415
"sigs.k8s.io/controller-runtime/pkg/client"
1516

17+
"github.com/google/uuid"
1618
. "github.com/onsi/ginkgo/v2"
1719
. "github.com/onsi/gomega"
1820
)
@@ -29,6 +31,18 @@ var _ = Describe("DeploymentRepository", func() {
2931
cfOrg = createOrgWithCleanup(ctx, prefixedGUID("org"))
3032
cfSpace = createSpaceWithCleanup(ctx, cfOrg.Name, prefixedGUID("space1"))
3133
cfApp = createApp(cfSpace.Name)
34+
Expect(k8sClient.Create(ctx, &korifiv1alpha1.AppWorkload{
35+
ObjectMeta: metav1.ObjectMeta{
36+
Namespace: cfApp.Namespace,
37+
Name: uuid.NewString(),
38+
Labels: map[string]string{
39+
korifiv1alpha1.CFAppGUIDLabelKey: cfApp.Name,
40+
},
41+
Annotations: map[string]string{
42+
version.KorifiCreationVersionKey: "0.7.2",
43+
},
44+
},
45+
})).To(Succeed())
3246

3347
deploymentRepo = repositories.NewDeploymentRepo(userClientFactory, namespaceRetriever)
3448
})
@@ -210,6 +224,24 @@ var _ = Describe("DeploymentRepository", func() {
210224
Expect(createErr).To(matchers.WrapErrorAssignableToTypeOf(apierrors.NotFoundError{}))
211225
})
212226
})
227+
228+
When("one of the app workloads does not support rolling deployments", func() {
229+
BeforeEach(func() {
230+
Expect(k8sClient.Create(ctx, &korifiv1alpha1.AppWorkload{
231+
ObjectMeta: metav1.ObjectMeta{
232+
Namespace: cfApp.Namespace,
233+
Name: uuid.NewString(),
234+
Labels: map[string]string{
235+
korifiv1alpha1.CFAppGUIDLabelKey: cfApp.Name,
236+
},
237+
},
238+
})).To(Succeed())
239+
})
240+
241+
It("returns an error", func() {
242+
Expect(createErr).To(BeAssignableToTypeOf(apierrors.UnprocessableEntityError{}))
243+
})
244+
})
213245
})
214246
})
215247
})

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ require (
6363
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
6464
github.com/Azure/go-autorest/logger v0.2.1 // indirect
6565
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
66-
github.com/Masterminds/semver/v3 v3.2.0 // indirect
66+
github.com/Masterminds/semver/v3 v3.2.0
6767
github.com/PaesslerAG/gval v1.0.0 // indirect
6868
github.com/aws/aws-sdk-go-v2 v1.18.0 // indirect
6969
github.com/aws/aws-sdk-go-v2/credentials v1.13.24 // indirect

helm/korifi/controllers/cf_roles/cf_admin.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,13 @@ rules:
160160
- patch
161161
- watch
162162

163+
- apiGroups:
164+
- korifi.cloudfoundry.org
165+
resources:
166+
- appworkloads
167+
verbs:
168+
- list
169+
163170
- apiGroups:
164171
- korifi.cloudfoundry.org
165172
resources:

helm/korifi/controllers/cf_roles/cf_space_developer.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ rules:
4646
- list
4747
- watch
4848

49+
- apiGroups:
50+
- korifi.cloudfoundry.org
51+
resources:
52+
- appworkloads
53+
verbs:
54+
- list
55+
4956
- apiGroups:
5057
- korifi.cloudfoundry.org
5158
resources:

version/version.go

+23
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,30 @@
11
package version
22

3+
import (
4+
"github.com/Masterminds/semver/v3"
5+
"sigs.k8s.io/controller-runtime/pkg/client"
6+
)
7+
38
const KorifiCreationVersionKey = "korifi.cloudfoundry.org/creation-version"
49

510
// version is overwritten at compile time by passing
611
// -ldflags -X code.cloudfoundry.org/korifi/version.Version=<version>
712
var Version = "v9999.99.99-local.dev"
13+
14+
type Checker struct {
15+
version *semver.Version
16+
}
17+
18+
func NewChecker(ver string) Checker {
19+
return Checker{version: semver.MustParse(ver)}
20+
}
21+
22+
func (c Checker) ObjectIsNewer(obj client.Object) (bool, error) {
23+
korifiVersion := obj.GetAnnotations()[KorifiCreationVersionKey]
24+
semVersion, err := semver.NewVersion(korifiVersion)
25+
if err != nil {
26+
return false, err
27+
}
28+
29+
return semVersion.GreaterThan(c.version), nil
30+
}

version/version_suite_test.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package version_test
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo/v2"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
func TestVersion(t *testing.T) {
11+
RegisterFailHandler(Fail)
12+
RunSpecs(t, "Version Suite")
13+
}

version/version_test.go

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package version_test
2+
3+
import (
4+
"code.cloudfoundry.org/korifi/version"
5+
. "github.com/onsi/ginkgo/v2"
6+
. "github.com/onsi/gomega"
7+
v1 "k8s.io/api/core/v1"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"sigs.k8s.io/controller-runtime/pkg/client"
10+
)
11+
12+
var _ = Describe("Version comparison", func() {
13+
var (
14+
objectVersion string
15+
checker version.Checker
16+
obj client.Object
17+
res bool
18+
err error
19+
)
20+
21+
BeforeEach(func() {
22+
objectVersion = ""
23+
})
24+
25+
JustBeforeEach(func() {
26+
checker = version.NewChecker("v0.7.1")
27+
obj = &v1.Pod{ObjectMeta: metav1.ObjectMeta{
28+
Annotations: map[string]string{
29+
version.KorifiCreationVersionKey: objectVersion,
30+
},
31+
}}
32+
res, err = checker.ObjectIsNewer(obj)
33+
})
34+
35+
When("object version is greater", func() {
36+
BeforeEach(func() {
37+
objectVersion = "v1.0.0"
38+
})
39+
40+
It("returns true", func() {
41+
Expect(res).To(BeTrue())
42+
})
43+
})
44+
45+
When("object version is less", func() {
46+
BeforeEach(func() {
47+
objectVersion = "v0.7.0"
48+
})
49+
50+
It("returns false", func() {
51+
Expect(res).To(BeFalse())
52+
})
53+
})
54+
55+
When("object version is the same", func() {
56+
BeforeEach(func() {
57+
objectVersion = "v0.7.1"
58+
})
59+
60+
It("returns false", func() {
61+
Expect(res).To(BeFalse())
62+
})
63+
})
64+
65+
When("object version is incorrect", func() {
66+
BeforeEach(func() {
67+
objectVersion = "foo"
68+
})
69+
70+
It("returns an error", func() {
71+
Expect(err).To(HaveOccurred())
72+
})
73+
})
74+
75+
When("object version is empty", func() {
76+
BeforeEach(func() {
77+
objectVersion = ""
78+
})
79+
80+
It("returns an error", func() {
81+
Expect(err).To(HaveOccurred())
82+
})
83+
})
84+
})
85+
86+
var _ = Describe("construction", func() {
87+
When("checker version is incorrect", func() {
88+
It("panics on construction", func() {
89+
Expect(func() { version.NewChecker("blah") }).To(Panic())
90+
})
91+
})
92+
})

0 commit comments

Comments
 (0)