-
Notifications
You must be signed in to change notification settings - Fork 57
Expand file tree
/
Copy pathfinal_pipeline_finalizer_removed.go
More file actions
345 lines (310 loc) · 16.1 KB
/
Copy pathfinal_pipeline_finalizer_removed.go
File metadata and controls
345 lines (310 loc) · 16.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
package service
import (
"context"
"encoding/json"
"fmt"
"time"
ecp "github.com/conforma/crds/api/v1alpha1"
"github.com/devfile/library/v2/pkg/util"
tektonutils "github.com/konflux-ci/release-service/tekton/utils"
tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
"go.yaml.in/yaml/v3"
releaseApi "github.com/konflux-ci/release-service/api/v1alpha1"
"github.com/konflux-ci/release-service/e2e-tests/pkg/constants"
"github.com/konflux-ci/release-service/e2e-tests/pkg/framework"
"github.com/konflux-ci/release-service/e2e-tests/pkg/utils"
releasecommon "github.com/konflux-ci/release-service/e2e-tests/tests/release"
ginkgo "github.com/onsi/ginkgo/v2"
gomega "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait"
)
const (
releasedImagePushRepo = "quay.io/redhat-appstudio-qe/dcmetromap"
sampleImage = "quay.io/redhat-appstudio-qe/dcmetromap@sha256:544259be8bcd9e6a2066224b805d854d863064c9b64fa3a87bfcd03f5b0f28e6"
gitSourceURL = "https://github.com/redhat-appstudio-qe/dc-metro-map-release"
gitSourceRevision = "d49914874789147eb2de9bb6a12cd5d150bfff92"
pipelineExamplesURL = "https://github.com/redhat-appstudio-qe/pipeline_examples"
pipelineExamplesRev = "main"
releasePlanNameFailure = constants.SourceReleasePlanName
releasePlanNameSuccess = constants.SourceReleasePlanName + "-success"
managedNamespaceFailure = "final-finalizer-managed-failure"
managedNamespaceSuccess = "final-finalizer-managed-success"
rpaNameFailure = constants.TargetReleasePlanAdmissionName
rpaNameSuccess = constants.TargetReleasePlanAdmissionName + "-success"
)
// scenarioParams is used by postReleaseVerification to find the right Release and managed namespace.
type scenarioParams struct {
releasePlanName string
managedNamespace string
expectManagedToFail bool
}
var (
fw *framework.Framework
err error
releaseCR *releaseApi.Release
devNamespace string
)
var _ = ginkgo.Describe("Finalizer is removed from final pipeline", releasecommon.LabelFinal, ginkgo.Ordered, func() {
defer ginkgo.GinkgoRecover()
ginkgo.AfterEach(framework.ReportFailure(&fw))
ginkgo.BeforeAll(func() {
setupFinalPipelineFinalizerSuite()
})
ginkgo.AfterAll(func() {
if !ginkgo.CurrentSpecReport().Failed() {
gomega.Expect(fw.AsKubeAdmin.CommonController.DeleteNamespace(managedNamespaceFailure)).To(gomega.Succeed())
gomega.Expect(fw.AsKubeAdmin.CommonController.DeleteNamespace(managedNamespaceSuccess)).To(gomega.Succeed())
gomega.Expect(fw.AsKubeAdmin.CommonController.DeleteNamespace(fw.UserNamespace)).To(gomega.Succeed())
}
})
ginkgo.Describe("when managed pipeline fails", func() {
postReleaseVerification(scenarioParams{
releasePlanName: releasePlanNameFailure,
managedNamespace: managedNamespaceFailure,
expectManagedToFail: true,
})
})
ginkgo.Describe("when managed pipeline succeeds", func() {
postReleaseVerification(scenarioParams{
releasePlanName: releasePlanNameSuccess,
managedNamespace: managedNamespaceSuccess,
expectManagedToFail: false,
})
})
})
// setupFinalPipelineFinalizerSuite creates one dev namespace with one Application, one tenant SA,
// one PVC, one Snapshot, and two ReleasePlans (failure and success). It creates two managed
// namespaces, each with its own release-service SA, EC policy, and ReleasePlanAdmission.
func setupFinalPipelineFinalizerSuite() {
fw, err = framework.NewFramework(utils.GetGeneratedNamespace("final-finalizer-dev"))
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "failed to create framework")
devNamespace = fw.UserNamespace
tenantPullSecretName := "final-finalizer-pull-secret"
tenantSAName := "final-finalizer-service-account"
// Create both managed namespaces and their release-service SAs and RPAs
for _, m := range []struct {
managedNs string
rpaName string
pipelinePath string
}{
{managedNamespaceFailure, rpaNameFailure, "pipelines/failing_pipeline.yaml"},
{managedNamespaceSuccess, rpaNameSuccess, "pipelines/simple_pipeline.yaml"},
} {
_, err = fw.AsKubeAdmin.CommonController.CreateTestNamespace(m.managedNs)
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "Error when creating managedNamespace: %v", err)
managedSA, err := fw.AsKubeAdmin.CommonController.CreateServiceAccount(constants.ReleasePipelineServiceAccountDefault, m.managedNs, nil, nil)
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "failed to create service account %s in %s: %v", constants.ReleasePipelineServiceAccountDefault, m.managedNs, err)
_, err = fw.AsKubeAdmin.ReleaseController.CreateReleasePipelineRoleBindingForServiceAccount(m.managedNs, managedSA)
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "failed to create rolebinding for service account: %v", err)
ecPolicyName := "ffinalizer-policy-" + util.GenerateRandomString(4)
defaultEcPolicy, err := fw.AsKubeAdmin.TektonController.GetEnterpriseContractPolicy("default", "enterprise-contract-service")
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "failed to get default EC policy: %v", err)
defaultEcPolicySpec := ecp.EnterpriseContractPolicySpec{
Description: "Red Hat's enterprise requirements",
PublicKey: fmt.Sprintf("k8s://%s/%s", m.managedNs, constants.PublicSecretNameAuth),
Sources: defaultEcPolicy.Spec.Sources,
Configuration: &ecp.EnterpriseContractPolicyConfiguration{
Collections: []string{"@slsa3"},
Exclude: []string{"step_image_registries", "tasks.required_tasks_found:prefetch-dependencies"},
},
}
_, err = fw.AsKubeAdmin.TektonController.CreateEnterpriseContractPolicy(ecPolicyName, m.managedNs, defaultEcPolicySpec)
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "failed to create EC policy %s: %v", ecPolicyName, err)
data, err := json.Marshal(map[string]interface{}{
"mapping": map[string]interface{}{
"components": []map[string]interface{}{
{
"component": constants.ComponentName,
"repository": releasedImagePushRepo,
},
},
},
})
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "failed to marshal RPA data: %v", err)
_, err = fw.AsKubeAdmin.ReleaseController.CreateReleasePlanAdmission(m.rpaName, m.managedNs, "", devNamespace, ecPolicyName, constants.ReleasePipelineServiceAccountDefault, []string{constants.ApplicationNameDefault}, false, &tektonutils.PipelineRef{
Resolver: "git",
Params: []tektonutils.Param{
{Name: "url", Value: pipelineExamplesURL},
{Name: "revision", Value: pipelineExamplesRev},
{Name: "pathInRepo", Value: m.pipelinePath},
},
}, &runtime.RawExtension{Raw: data})
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "failed to create ReleasePlanAdmission %s: %v", m.rpaName, err)
}
// Single tenant secret and SA in dev namespace
sourceAuthJson := utils.GetEnv("QUAY_TOKEN", "")
gomega.Expect(sourceAuthJson).ToNot(gomega.BeEmpty(), "QUAY_TOKEN env var is required (dockerconfigjson format)")
_, err = fw.AsKubeAdmin.CommonController.GetSecret(devNamespace, tenantPullSecretName)
if errors.IsNotFound(err) {
_, err = fw.AsKubeAdmin.CommonController.CreateRegistryAuthSecret(tenantPullSecretName, devNamespace, sourceAuthJson)
gomega.Expect(err).ToNot(gomega.HaveOccurred(), "failed to create secret %s: %v", tenantPullSecretName, err)
}
gomega.Expect(err).ToNot(gomega.HaveOccurred())
_, err = fw.AsKubeAdmin.CommonController.CreateServiceAccount(tenantSAName, devNamespace, []corev1.ObjectReference{{Name: tenantPullSecretName}}, nil)
gomega.Expect(err).ToNot(gomega.HaveOccurred(), "failed to create service account %s in %s: %v", tenantSAName, devNamespace, err)
// Single Application in dev namespace
_, err = fw.AsKubeAdmin.KonfluxApiController.CreateApplication(constants.ApplicationNameDefault, devNamespace)
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "failed to create Application %s: %v", constants.ApplicationNameDefault, err)
data, err := json.Marshal(map[string]interface{}{
"mapping": map[string]interface{}{
"components": []map[string]interface{}{
{
"component": constants.ComponentName,
"repository": releasedImagePushRepo,
},
},
},
})
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "failed to marshal RP data: %v", err)
finalPipeline := &tektonutils.ParameterizedPipeline{}
finalPipeline.ServiceAccountName = tenantSAName
finalPipeline.Timeouts = tektonv1.TimeoutFields{
Pipeline: &metav1.Duration{Duration: 10 * time.Minute},
}
finalPipeline.PipelineRef = tektonutils.PipelineRef{
Resolver: "git",
Params: []tektonutils.Param{
{Name: "url", Value: pipelineExamplesURL},
{Name: "revision", Value: pipelineExamplesRev},
{Name: "pathInRepo", Value: "pipelines/simple_pipeline.yaml"},
},
UseEmptyDir: true,
}
// Two ReleasePlans in dev namespace (same final pipeline, different targets)
_, err = fw.AsKubeAdmin.ReleaseController.CreateReleasePlan(releasePlanNameFailure, devNamespace, constants.ApplicationNameDefault, managedNamespaceFailure, "", &runtime.RawExtension{Raw: data}, nil, finalPipeline, nil)
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "failed to create ReleasePlan %s: %v", releasePlanNameFailure, err)
_, err = fw.AsKubeAdmin.ReleaseController.CreateReleasePlan(releasePlanNameSuccess, devNamespace, constants.ApplicationNameDefault, managedNamespaceSuccess, "", &runtime.RawExtension{Raw: data}, nil, finalPipeline, nil)
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "failed to create ReleasePlan %s: %v", releasePlanNameSuccess, err)
_, err = fw.AsKubeAdmin.TektonController.CreatePVCInAccessMode(constants.ReleasePvcName, devNamespace, corev1.ReadWriteOnce)
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "failed to create PVC %s: %v", constants.ReleasePvcName, err)
_, err = fw.AsKubeAdmin.IntegrationController.CreateSnapshotWithImageSource(constants.ComponentName, constants.ApplicationNameDefault, devNamespace, sampleImage, gitSourceURL, gitSourceRevision, "", "", "", "")
gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "failed to create Snapshot: %v", err)
}
// getReleaseByRPName returns the Release in devNamespace whose spec.releasePlan matches releasePlanName.
func getReleaseByRPName(fw *framework.Framework, devNamespace, releasePlanName string) (*releaseApi.Release, error) {
list, err := fw.AsKubeAdmin.ReleaseController.GetReleasesInNamespace(devNamespace)
if err != nil {
return nil, err
}
for i := range list.Items {
r := &list.Items[i]
if r.Spec.ReleasePlan == releasePlanName {
return r, nil
}
}
return nil, fmt.Errorf("no Release found with spec.releasePlan %q in namespace %s", releasePlanName, devNamespace)
}
// postReleaseVerification registers the shared "Post-release verification" Its.
// It finds the Release for this scenario by releasePlanName and uses the scenario's managedNamespace.
func postReleaseVerification(p scenarioParams) {
var _ = ginkgo.Describe("Post-release verification", func() {
ginkgo.AfterEach(func() {
if ginkgo.CurrentSpecReport().Failed() && releaseCR != nil {
release, getErr := fw.AsKubeAdmin.ReleaseController.GetRelease(releaseCR.GetName(), devNamespace)
if getErr == nil {
releaseYaml, marshalErr := yaml.Marshal(release)
if marshalErr == nil {
ginkgo.GinkgoWriter.Printf("Release CR (spec failed):\n%s\n", string(releaseYaml))
}
}
}
})
ginkgo.It("verifies that a Release CR should have been created in the dev namespace", func() {
gomega.Eventually(func() error {
var getErr error
releaseCR, getErr = getReleaseByRPName(fw, devNamespace, p.releasePlanName)
return getErr
}, constants.ReleaseCreationTimeout, constants.DefaultInterval).Should(gomega.Succeed())
})
ginkgo.It("verifies that managed PipelineRun finished with expected outcome", func() {
assertManagedPipelineRunFinished(fw, p.managedNamespace, p.expectManagedToFail)
})
ginkgo.It("verifies that the Released condition on the Release is no longer Progressing.", func() {
gomega.Eventually(func() error {
var getErr error
releaseCR, getErr = getReleaseByRPName(fw, devNamespace, p.releasePlanName)
if getErr != nil {
return getErr
}
if releaseCR.IsReleasing() {
return fmt.Errorf("release %s/%s is not marked as finished yet", releaseCR.GetNamespace(), releaseCR.GetName())
}
return nil
}, constants.ReleaseCreationTimeout, constants.DefaultInterval).Should(gomega.Succeed())
})
ginkgo.It("verifies that the finalizer was removed from the final pipeline", func() {
assertFinalizerRemovedFromFinalPipeline(fw, devNamespace)
})
})
}
func assertManagedPipelineRunFinished(fw *framework.Framework, managedNamespace string, expectFailed bool) {
var debugPrinted bool
gomega.Eventually(func() error {
managedPipelineRun, err := fw.AsKubeAdmin.TektonController.GetPipelineRunInNamespace(managedNamespace, releaseCR.GetName(), releaseCR.GetNamespace())
if err != nil {
if !debugPrinted {
debugPrinted = true
prList, listErr := fw.AsKubeAdmin.TektonController.GetAllPipelineRunsInNamespace(managedNamespace)
if listErr == nil {
ginkgo.GinkgoWriter.Printf("PipelineRuns in %s: %d total\n", managedNamespace, len(prList.Items))
for i := range prList.Items {
pr := &prList.Items[i]
ginkgo.GinkgoWriter.Printf(" - %s labels=%v\n", pr.GetName(), pr.GetLabels())
}
} else {
ginkgo.GinkgoWriter.Printf("Failed to list PipelineRuns in %s: %v\n", managedNamespace, listErr)
}
}
return fmt.Errorf("managed PipelineRun not found for release %s/%s: %w", releaseCR.GetNamespace(), releaseCR.GetName(), err)
}
if !managedPipelineRun.IsDone() {
return fmt.Errorf("managed PipelineRun %s/%s has not finished yet", managedPipelineRun.GetNamespace(), managedPipelineRun.GetName())
}
if expectFailed && !framework.HasPipelineRunFailed(managedPipelineRun) {
return fmt.Errorf("expected managed PipelineRun %s/%s to have failed", managedPipelineRun.GetNamespace(), managedPipelineRun.GetName())
}
if !expectFailed && !framework.HasPipelineRunSucceeded(managedPipelineRun) {
return fmt.Errorf("expected managed PipelineRun %s/%s to have succeeded", managedPipelineRun.GetNamespace(), managedPipelineRun.GetName())
}
return nil
}, 15*time.Minute, constants.DefaultInterval).Should(gomega.Succeed(),
"managed release PipelineRun should have finished with expected outcome")
}
func assertFinalizerRemovedFromFinalPipeline(fw *framework.Framework, devNamespace string) {
err := wait.PollUntilContextTimeout(context.Background(), constants.PipelineRunPollingInterval, 15*time.Minute, true, func(ctx context.Context) (done bool, err error) {
finalPipelineRun, err := fw.AsKubeAdmin.TektonController.GetPipelineRunInNamespace(devNamespace, releaseCR.GetName(), devNamespace)
if err != nil {
ginkgo.GinkgoWriter.Printf("Final PipelineRun has not been created yet for release %s/%s\n", devNamespace, releaseCR.GetName())
return false, nil
}
for _, condition := range finalPipelineRun.Status.Conditions {
ginkgo.GinkgoWriter.Printf("PipelineRun %s reason: %s\n", finalPipelineRun.Name, condition.Reason)
}
if !finalPipelineRun.IsDone() {
return false, nil
}
return true, nil
})
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "timed out waiting for final PipelineRun to complete")
gomega.Eventually(func() error {
finalPipelineRun, err := fw.AsKubeAdmin.TektonController.GetPipelineRunInNamespace(devNamespace, releaseCR.GetName(), devNamespace)
if err != nil {
return fmt.Errorf("failed to get final PipelineRun for release %s/%s: %w", releaseCR.GetNamespace(), releaseCR.GetName(), err)
}
finalizers := finalPipelineRun.GetFinalizers()
if len(finalizers) > 0 {
ginkgo.GinkgoWriter.Printf("Final PipelineRun %s/%s finalizers: %v\n", finalPipelineRun.GetNamespace(), finalPipelineRun.GetName(), finalizers)
}
for _, f := range finalizers {
if f == "appstudio.redhat.com/release-finalizer" {
return fmt.Errorf("release finalizer still present on final PipelineRun %s/%s", finalPipelineRun.GetNamespace(), finalPipelineRun.GetName())
}
}
return nil
}, 30*time.Second, constants.DefaultInterval).Should(gomega.Succeed(),
"release finalizer should have been removed from final pipeline within 30 seconds")
}