1 package bannerctl
2
3 import (
4 "context"
5 "database/sql"
6 "encoding/json"
7 "fmt"
8 "net/http"
9 "net/http/httptest"
10 "os"
11 "regexp"
12 "strings"
13 "testing"
14 "time"
15
16 metricsscope "cloud.google.com/go/monitoring/metricsscope/apiv1"
17 "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
18 registryAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/artifactregistry/v1beta1"
19 computeAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/compute/v1beta1"
20 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/iam/v1beta1"
21
22 k8sAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/k8s/v1alpha1"
23 loggingAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/logging/v1beta1"
24 resourceAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/resourcemanager/v1beta1"
25 serviceAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/serviceusage/v1beta1"
26 storageAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/storage/v1beta1"
27 "github.com/google/go-cmp/cmp"
28 "github.com/stretchr/testify/assert"
29 "github.com/stretchr/testify/suite"
30 "google.golang.org/api/option"
31 corev1 "k8s.io/api/core/v1"
32 "k8s.io/apimachinery/pkg/api/errors"
33 kmeta "k8s.io/apimachinery/pkg/api/meta"
34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35 "k8s.io/apimachinery/pkg/types"
36 ctrl "sigs.k8s.io/controller-runtime"
37 "sigs.k8s.io/controller-runtime/pkg/client"
38
39 "edge-infra.dev/pkg/edge/api/graph/model"
40 "edge-infra.dev/pkg/edge/api/testutils/seededpostgres"
41 bannerAPI "edge-infra.dev/pkg/edge/apis/banner/v1alpha1"
42 syncedobjectApi "edge-infra.dev/pkg/edge/apis/syncedobject/apis/v1alpha1"
43 capabilities "edge-infra.dev/pkg/edge/capabilities"
44 edgeconstants "edge-infra.dev/pkg/edge/constants"
45 bannerconstants "edge-infra.dev/pkg/edge/constants/api/banner"
46 "edge-infra.dev/pkg/edge/controllers/dbmetrics"
47 "edge-infra.dev/pkg/edge/controllers/util/edgedb"
48 "edge-infra.dev/pkg/edge/gcpinfra"
49 "edge-infra.dev/pkg/edge/registration"
50 "edge-infra.dev/pkg/f8n/gcp/k8s/controllers/dennis"
51 "edge-infra.dev/pkg/k8s/konfigkonnector/apis/meta"
52 "edge-infra.dev/pkg/k8s/meta/status"
53 "edge-infra.dev/pkg/k8s/runtime/controller"
54 "edge-infra.dev/pkg/k8s/runtime/inventory"
55 ff "edge-infra.dev/pkg/lib/featureflag"
56 fftest "edge-infra.dev/pkg/lib/featureflag/testutil"
57 "edge-infra.dev/pkg/lib/gcp/iam/roles"
58 gcpProject "edge-infra.dev/pkg/lib/gcp/project"
59 "edge-infra.dev/pkg/lib/logging"
60 "edge-infra.dev/pkg/lib/uuid"
61 "edge-infra.dev/test"
62 "edge-infra.dev/test/framework"
63 "edge-infra.dev/test/framework/gcp"
64 "edge-infra.dev/test/framework/integration"
65 "edge-infra.dev/test/framework/k8s"
66 "edge-infra.dev/test/framework/k8s/envtest"
67 )
68
69 const DefaultSecretValue = "mock secret value"
70
71 var TestProjectNumber = "123456789000"
72
73 func TestMain(m *testing.M) {
74 framework.HandleFlags()
75
76 ctrl.SetLogger(logging.NewLogger().Logger)
77 os.Exit(m.Run())
78 }
79
80 type Suite struct {
81 *framework.Framework
82 *k8s.K8s
83 ctx context.Context
84 timeout time.Duration
85 tick time.Duration
86 projectID string
87 sMgr *mockSecretManager
88 msc metricsScopesClient
89
90 db *sql.DB
91 }
92
93 type mockSecretManager struct {
94 clients map[string]*mockSecretManagerClient
95 }
96
97 func (sm *mockSecretManager) NewWithOptions(_ context.Context, projectID string, _ ...option.ClientOption) (secretManagerClient, error) {
98 if sm.clients[projectID] == nil {
99 sm.clients[projectID] = &mockSecretManagerClient{
100 secrets: make(map[string]*mockSecret),
101 }
102 }
103 return sm.clients[projectID], nil
104 }
105
106 type mockSecretManagerClient struct {
107 secrets map[string]*mockSecret
108 }
109
110 type mockSecret struct {
111 value []byte
112 }
113
114
115
116
117 func (smc *mockSecretManagerClient) GetLatestSecretValue(_ context.Context, secretID string) ([]byte, error) {
118 s := smc.secrets[secretID]
119 if s == nil {
120 return nil, fmt.Errorf("secret %s not found", secretID)
121 }
122 return smc.secrets[secretID].value, nil
123 }
124
125
126 func (smc *mockSecretManagerClient) GetLatestSecretValueInfo(_ context.Context, secretID string) (*secretmanagerpb.SecretVersion, error) {
127 s := smc.secrets[secretID]
128 if s == nil {
129 return nil, fmt.Errorf("secret %s not found", secretID)
130 }
131 return &secretmanagerpb.SecretVersion{
132 Name: secretID,
133 }, nil
134 }
135
136 func (smc *mockSecretManagerClient) GetSecret(_ context.Context, secretID string) (*secretmanagerpb.Secret, error) {
137 s := smc.secrets[secretID]
138 if s == nil {
139 return nil, fmt.Errorf("secret %s not found", secretID)
140 }
141 spb := &secretmanagerpb.Secret{
142 Name: secretID,
143 }
144 return spb, nil
145 }
146
147 func (smc *mockSecretManagerClient) AddSecret(_ context.Context, secretID string, _ []byte, _ map[string]string, _ bool, _ *time.Time, _ string) error {
148 smc.secrets[secretID] = &mockSecret{value: []byte(DefaultSecretValue)}
149 return nil
150 }
151
152 type mockMetricsScopeClient struct{}
153
154 func (c *mockMetricsScopeClient) AddMonitoredProject(_ context.Context, _ string) (*metricsscope.CreateMonitoredProjectOperation, error) {
155 return &metricsscope.CreateMonitoredProjectOperation{}, nil
156 }
157
158 func falsifyResourceReadiness(conds []k8sAPI.Condition) []k8sAPI.Condition {
159 hasCond := false
160 for _, c := range conds {
161 if c.Type == "Ready" {
162 hasCond = true
163 }
164 }
165 if !hasCond {
166 conds = append(conds, k8sAPI.Condition{Type: "Ready", Status: "True"})
167 }
168 return conds
169 }
170
171 func isOwnedByBanner(obj client.Object, banner string) bool {
172 for _, o := range obj.GetOwnerReferences() {
173 if o.Name == banner {
174 return true
175 }
176 }
177 return false
178 }
179
180 func TestBannerController(t *testing.T) {
181 testEnv := envtest.Setup()
182 cfg, opts := controller.ProcessOptions(controller.WithCfg(testEnv.Config), controller.WithMetricsAddress("0"))
183 opts.Scheme = createScheme()
184 mgr, err := ctrl.NewManager(cfg, opts)
185 test.NoError(err)
186
187
188 fftest.MustInitTestFeatureFlagsForContexts([]fftest.TestFlag{
189 {
190 FlagContext: ff.NewEnvironmentContext(),
191 Name: ff.UseBannerCTLPruning,
192 Value: true,
193 },
194 })
195
196 f := framework.New("bannerctl").Component("bannerctl")
197
198 sp, err := seededpostgres.New()
199 test.NoError(err)
200 defer sp.Close()
201
202 db, err := sp.DB()
203 test.NoError(err)
204 defer db.Close()
205
206
207 s := &Suite{
208 Framework: f,
209 ctx: context.Background(),
210 timeout: 10 * time.Second,
211 tick: 50 * time.Millisecond,
212 db: db,
213 }
214
215
216 if integration.IsIntegrationTest() {
217 s.projectID = gcp.GCloud.ProjectID
218 } else {
219 s.projectID = "mock gcp projectid"
220 s.msc = &mockMetricsScopeClient{}
221 }
222 s.sMgr = &mockSecretManager{clients: make(map[string]*mockSecretManagerClient)}
223 for _, secretID := range edgeconstants.PlatformSecretIDs {
224 c, _ := s.sMgr.NewWithOptions(s.ctx, s.projectID)
225 _ = c.AddSecret(s.ctx, secretID, []byte("mock secret value"), nil, false, nil, "")
226 }
227
228 totpSecret := "totp-secret"
229 srv := httptest.NewServer(http.HandlerFunc(
230 registration.GraphQLHandler(assert.New(t),
231 registration.WithTotpSecret(totpSecret),
232 registration.WithExistingCluster("couchdb-enabled"),
233 )))
234 defer srv.Close()
235
236 bslSrv, bslClient := FakeBslClient()
237 defer bslSrv.Close()
238
239 dbm := dbmetrics.New("bannerctl")
240
241 reconciler := &BannerReconciler{
242 Client: mgr.GetClient(),
243 Log: ctrl.Log.WithName("bannerctl"),
244 ForemanProjectID: s.projectID,
245 PlatInfraProjectID: s.projectID,
246 SecretManager: s.sMgr,
247 MetricsScopesClient: s.msc,
248 DefaultRequeue: 10 * time.Millisecond,
249 Name: "bannerctl",
250 EdgeAPI: srv.URL,
251 TotpSecret: totpSecret,
252 Domain: "dev0.edge-preprod.dev",
253 DatasyncDNSName: datasyncDNSName,
254 DatasyncDNSZone: datasyncDNSZone,
255 DatabaseName: databaseName,
256 Conditions: bannerConditions,
257 EdgeDB: &edgedb.EdgeDB{DB: db},
258 Recorder: dbm,
259 BSLClient: bslClient,
260 GCPRegion: gcpRegion,
261 GCPZone: gcpZone,
262 GCPForemanProjectNumber: gcpForemanProjectNumber,
263 BSLConfig: bslConfig,
264 EdgeSecOptInCompliance: true,
265 EdgeSecMaxLeasePeriod: "48h",
266 EdgeSecMaxValidityPeriod: "60d",
267 }
268 err = reconciler.SetupWithManager(mgr)
269 test.NoError(err)
270
271 k := k8s.New(testEnv.Config, k8s.WithCtrlManager(mgr), k8s.WithKonfigKonnector())
272 s.K8s = k
273 f.Register(k)
274
275 suite.Run(t, s)
276
277 t.Cleanup(func() {
278 f.NoError(testEnv.Stop())
279 })
280 }
281
282 func (s *Suite) TestBannerCreation_SecondaryResources() {
283 bannerGUID := uuid.New().UUID
284 generatedProjID := fmt.Sprintf("%s-%s", gcpinfra.ProjectIDPrefix, gcpProject.RandAN(29-(len(gcpinfra.ProjectIDPrefix))))
285 testBSLID := uuid.New().UUID
286 testOrg := uuid.New().UUID
287 banner := &bannerAPI.Banner{
288 ObjectMeta: metav1.ObjectMeta{
289 Name: bannerGUID,
290 },
291 Spec: bannerAPI.BannerSpec{
292 DisplayName: "testy-mcbanner",
293 GCP: bannerAPI.GCPConfig{
294 ProjectID: generatedProjID,
295 },
296 BSL: bannerAPI.BSLConfig{
297 EnterpriseUnit: bannerAPI.BSLEnterpriseUnit{
298 ID: testBSLID,
299 },
300 Organization: bannerAPI.BSLOrganization{
301 Name: testOrg,
302 },
303 },
304 Enablements: []string{CouchDBEnablement},
305 },
306 }
307
308 s.createBanner(banner)
309 defer s.deleteBanner(banner)
310
311
312
313 project := &resourceAPI.Project{
314 ObjectMeta: metav1.ObjectMeta{
315 Name: projectName,
316 Namespace: bannerGUID,
317 },
318 }
319 s.Require().Eventually(func() bool {
320 err := s.Client.Get(s.ctx, types.NamespacedName{
321 Name: project.Name,
322 Namespace: project.Namespace,
323 }, project)
324 return err == nil
325 }, s.timeout, s.tick, "expected Project was never found")
326 s.Require().True(isOwnedByBanner(project, bannerGUID), "expected (namespaced) Project to be owned by (cluster scoped) Banner")
327
328 project.Status.Conditions = falsifyResourceReadiness(project.Status.Conditions)
329 project.Status.Number = &TestProjectNumber
330 s.Require().NoError(s.Client.Update(s.ctx, project))
331 s.Require().Eventually(func() bool {
332 err := s.Client.Get(s.ctx, types.NamespacedName{
333 Name: banner.Name,
334 Namespace: banner.Namespace,
335 }, banner)
336 return err == nil && banner.Status.ProjectRef != ""
337 }, s.timeout, s.tick, "expected Banner Status was never set")
338
339
340 projectViaStatusRef := &resourceAPI.Project{}
341 s.Require().Eventually(func() bool {
342 err := s.Client.Get(s.ctx, types.NamespacedName{
343 Name: strings.Split(banner.Status.ProjectRef, "/")[1],
344 Namespace: strings.Split(banner.Status.ProjectRef, "/")[0],
345 }, projectViaStatusRef)
346 return err == nil
347 }, s.timeout, s.tick, "expected Project was never found")
348 s.Equal(projectViaStatusRef, project, "could not find Project obj via Banner.Status.ProjectRef")
349
350
351 namespace := &corev1.Namespace{}
352 s.Require().Eventually(func() bool {
353 err := s.Client.Get(s.ctx, types.NamespacedName{
354 Name: bannerGUID,
355 }, namespace)
356 if err != nil {
357 return false
358 }
359 nsAnnos := namespace.Annotations
360 if nsAnnos != nil {
361 return s.Equal(generatedProjID, namespace.Annotations[meta.ProjectAnnotation]) && s.Equal(meta.DeletionPolicyAbandon, namespace.Annotations[meta.DeletionPolicyAnnotation])
362 }
363 return false
364 }, s.timeout, s.tick, "expected Namespace was never found or didnt have correct annotation")
365
366
367
368
369
370
371
372
373
374
375
376 s.Require().Eventually(func() bool {
377 for _, api := range gcpinfra.TenantAPIs {
378 service := &serviceAPI.Service{}
379 err := s.Client.Get(s.ctx, types.NamespacedName{
380 Name: api,
381 Namespace: bannerGUID,
382 }, service)
383 if errors.IsNotFound(err) {
384 return false
385 }
386 s.Require().True(isOwnedByBanner(service, bannerGUID), "expected Service to be owned by Banner")
387 s.Require().Equal(meta.DeletionPolicyAbandon, service.Annotations[meta.DeletionPolicyAnnotation])
388
389
390 service.Status.Conditions = falsifyResourceReadiness(service.Status.Conditions)
391 s.Require().NoError(s.Client.Update(s.ctx, service))
392 }
393 return true
394 }, s.timeout, s.tick, "not all Services were created (enabled)")
395
396
397 sb := &storageAPI.StorageBucket{}
398 s.Require().Eventually(func() bool {
399 err := s.Client.Get(s.ctx, types.NamespacedName{
400 Name: generatedProjID,
401 Namespace: bannerGUID,
402 }, sb)
403 return err == nil
404 }, s.timeout, s.tick, "expected StorageBucket was never found")
405 s.Require().True(isOwnedByBanner(sb, bannerGUID), "expected StorageBucket to be owned by Banner")
406 s.Require().Equal(meta.DeletionPolicyAbandon, sb.Annotations[meta.DeletionPolicyAnnotation])
407
408
409 if integration.IsIntegrationTest() {
410
411
412 for _, secretID := range edgeconstants.PlatformSecretIDs {
413 v, err := s.sMgr.clients[bannerGUID].GetLatestSecretValue(s.ctx, secretID)
414 s.NoError(err)
415 s.Equal(DefaultSecretValue, string(v))
416 }
417 } else {
418
419 s.Require().Eventually(func() bool {
420 c := s.sMgr.clients[generatedProjID]
421 if c == nil {
422 return false
423 }
424 for _, secretID := range edgeconstants.PlatformSecretIDs {
425 v, err := c.GetLatestSecretValue(s.ctx, secretID)
426 if err != nil {
427 return false
428 }
429 if !s.Equal(DefaultSecretValue, string(v)) {
430 return false
431 }
432 }
433 return true
434 }, s.timeout, s.tick, "never got a generated GCP Project ID")
435 }
436
437
438 cAddr := &computeAPI.ComputeAddress{
439 ObjectMeta: metav1.ObjectMeta{
440 Name: fmt.Sprintf("%s-%s", bannerconstants.RemoteAccessIPName, banner.Name),
441 Namespace: bannerGUID,
442 },
443 }
444 s.Require().Eventually(func() bool {
445 err := s.Client.Get(s.ctx, types.NamespacedName{
446 Name: cAddr.Name,
447 Namespace: cAddr.Namespace,
448 }, cAddr)
449 return err == nil
450 }, s.timeout, s.tick, fmt.Sprintf("expected ComputeAddress %s/%s was never found", cAddr.Namespace, cAddr.Name))
451 dnsRecordCfgs := cAddr.Annotations[dennis.RecordConfigsAnnotation]
452 s.Require().NotEmpty(dnsRecordCfgs, fmt.Sprintf("ComputeAddress was missing required annotation %s", dennis.RecordConfigsAnnotation))
453 cfgs := &[]dennis.RecordConfig{}
454 err := json.Unmarshal([]byte(dnsRecordCfgs), cfgs)
455 s.Require().NoError(err, fmt.Sprintf("ComputeAddress had invalid %s annotation", dennis.RecordConfigsAnnotation))
456 s.Require().Equal(meta.DeletionPolicyAbandon, cAddr.Annotations[meta.DeletionPolicyAnnotation])
457
458
459
460 re := regexp.MustCompile(`((([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.){2}([a-zA-Z]{2,6}|([a-zA-Z0-9-]){2,30}\.[a-zA-Z]{2,3})\.$`)
461 for _, cfg := range *cfgs {
462 s.Require().True(re.MatchString(cfg.Name), fmt.Sprintf("remote access dns name '%s' was not valid", cfg.Name))
463 }
464
465
466 lls := &loggingAPI.LoggingLogSink{}
467 s.Require().Eventually(func() bool {
468 err := s.Client.Get(s.ctx, types.NamespacedName{
469 Name: logSinkName,
470 Namespace: bannerGUID,
471 }, lls)
472 return err == nil
473 }, s.timeout, s.tick, "expected LoggingLogSink was never found")
474 s.Require().True(isOwnedByBanner(lls, bannerGUID), "expected LoggingLogSink to be owned by Banner")
475 s.Require().Equal(fmt.Sprintf("storage.googleapis.com/%s-siem", s.projectID), lls.Spec.Destination.StorageBucketRef.External)
476 s.Require().Equal(banner.Spec.GCP.ProjectID, lls.Spec.ProjectRef.External)
477 s.Require().True(*lls.Spec.UniqueWriterIdentity, "expected LoggingLogSink UniqueWriterIdentity to be true")
478
479
480
481
482 s.Require().Eventually(func() bool {
483 err := s.Client.Get(s.ctx, types.NamespacedName{
484 Name: banner.Name,
485 Namespace: banner.Namespace,
486 }, banner)
487 return err == nil && kmeta.IsStatusConditionTrue(banner.Status.Conditions, status.ReadyCondition)
488 }, s.timeout, s.tick, "Banner never became Ready")
489
490 namespaceSO := &syncedobjectApi.SyncedObject{}
491 s.Require().Eventually(func() bool {
492 err := s.Client.Get(s.ctx, types.NamespacedName{
493 Name: edgeconstants.BannerWideNamespace,
494 Namespace: banner.Name,
495 }, namespaceSO)
496 return err == nil
497 }, s.timeout, s.tick, "expected namespace synced obj was never found")
498 s.Equal(namespaceSO.Spec.Cluster, "", "namespace synced obj does not have expected empty cluster")
499 s.Equal(namespaceSO.Spec.Banner, banner.Spec.GCP.ProjectID, "namespace synced obj does not have expected project")
500
501
502 expectedForemanSO := []*syncedobjectApi.SyncedObject{
503
504 {
505 ObjectMeta: metav1.ObjectMeta{
506 Name: fmt.Sprintf("banner-proxy-%s", banner.Name),
507 Namespace: banner.Name,
508 },
509 },
510 }
511
512
513 for _, so := range expectedForemanSO {
514 s.pollForSyncedObject(so)
515 s.Require().Equal(s.projectID, so.Spec.Banner, fmt.Sprintf("SyncedObject %s/%s does not have banner set", so.Name, so.Namespace))
516 s.Require().Equal("foreman0", so.Spec.Cluster, fmt.Sprintf("synced object %s/%s does not have cluster set", so.Name, so.Namespace))
517 }
518
519
520 var hash string
521 var kccResourceName string
522
523 infraIAMSA := &v1beta1.IAMServiceAccount{}
524 s.Require().Eventually(func() bool {
525 if banner.Status.ClusterInfraClusterEdgeID != "" {
526 hash = uuid.FromUUID(banner.Status.ClusterInfraClusterEdgeID).Hash()
527 kccResourceName = fmt.Sprintf("kcc-%s", hash)
528 }
529 err := s.Client.Get(s.ctx, types.NamespacedName{
530 Name: kccResourceName,
531 Namespace: bannerGUID,
532 }, infraIAMSA)
533 return err == nil
534 }, s.timeout, s.tick, "expected kcc IAMServiceAccount was never found")
535 s.Require().True(isOwnedByBanner(infraIAMSA, bannerGUID), "expected IAMServiceAccount to be owned by Banner")
536 s.Require().Equal(meta.DeletionPolicyAbandon, infraIAMSA.Annotations[meta.DeletionPolicyAnnotation])
537 s.Require().Equal(fmt.Sprintf("k8s cfg connector for project %s", banner.Spec.DisplayName), *infraIAMSA.Spec.DisplayName)
538
539 projectOwner := &v1beta1.IAMPolicyMember{}
540 s.Require().Eventually(func() bool {
541 err := s.Client.Get(s.ctx, types.NamespacedName{
542 Name: fmt.Sprintf("%s-owner", kccResourceName),
543 Namespace: bannerGUID,
544 }, projectOwner)
545 return err == nil
546 }, s.timeout, s.tick, "expected kcc project owner IAMPolicyMember was never found")
547 s.Require().True(isOwnedByBanner(projectOwner, bannerGUID), "expected IAMPolicyMember to be owned by Banner")
548 s.Require().Equal(meta.DeletionPolicyAbandon, projectOwner.Annotations[meta.DeletionPolicyAnnotation])
549 s.Require().Equal(fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", kccResourceName, generatedProjID), *projectOwner.Spec.Member)
550 s.Require().Equal(resourceAPI.SchemeGroupVersion.String(), projectOwner.Spec.ResourceRef.APIVersion)
551 s.Require().Equal(resourceAPI.ProjectGVK.Kind, projectOwner.Spec.ResourceRef.Kind)
552 s.Require().Equal(generatedProjID, projectOwner.Spec.ResourceRef.External)
553 s.Require().Equal(roles.Owner, projectOwner.Spec.Role)
554
555 logWriter := &v1beta1.IAMPolicyMember{}
556 policyName := fmt.Sprintf("%s-logging-logwriter", kccResourceName)
557 s.Require().Eventually(func() bool {
558 err := s.Client.Get(s.ctx, types.NamespacedName{
559 Name: policyName,
560 Namespace: bannerGUID,
561 }, logWriter)
562 return err == nil
563 }, s.timeout, s.tick, "expected kcc project owner IAMPolicyMember was never found")
564 s.Require().True(isOwnedByBanner(logWriter, bannerGUID), "expected IAMPolicyMember to be owned by Banner")
565 s.Require().Equal(meta.DeletionPolicyAbandon, logWriter.Annotations[meta.DeletionPolicyAnnotation])
566 s.Require().Equal(fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", kccResourceName, generatedProjID), *logWriter.Spec.Member)
567 s.Require().Equal(resourceAPI.SchemeGroupVersion.String(), logWriter.Spec.ResourceRef.APIVersion)
568 s.Require().Equal(resourceAPI.ProjectGVK.Kind, logWriter.Spec.ResourceRef.Kind)
569 s.Require().Equal(s.projectID, logWriter.Spec.ResourceRef.External)
570 s.Require().Equal(roles.ProjectAdmin, logWriter.Spec.Role)
571 s.Require().Equal("kcc_delegated_foreman_roles", logWriter.Spec.Condition.Title)
572 s.Require().Equal("KCC delegated roles on foreman", *logWriter.Spec.Condition.Description)
573 s.Require().Equal(KccDelegatedRolesExpression, logWriter.Spec.Condition.Expression)
574
575 pubSubAdmin := &v1beta1.IAMPolicyMember{}
576 policyName = fmt.Sprintf("%s-pubsub-admin", kccResourceName)
577 s.Require().Eventually(func() bool {
578 err := s.Client.Get(s.ctx, types.NamespacedName{
579 Name: policyName,
580 Namespace: bannerGUID,
581 }, pubSubAdmin)
582 return err == nil
583 }, s.timeout, s.tick, "expected kcc project owner IAMPolicyMember was never found")
584 s.Require().True(isOwnedByBanner(pubSubAdmin, bannerGUID), "expected IAMPolicyMember to be owned by Banner")
585 s.Require().Equal(meta.DeletionPolicyAbandon, pubSubAdmin.Annotations[meta.DeletionPolicyAnnotation])
586 s.Require().Equal(fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", kccResourceName, generatedProjID), *pubSubAdmin.Spec.Member)
587 s.Require().Equal(resourceAPI.SchemeGroupVersion.String(), pubSubAdmin.Spec.ResourceRef.APIVersion)
588 s.Require().Equal(resourceAPI.ProjectGVK.Kind, pubSubAdmin.Spec.ResourceRef.Kind)
589 s.Require().Equal(s.projectID, pubSubAdmin.Spec.ResourceRef.External)
590 s.Require().Equal(roles.PubsubAdmin, pubSubAdmin.Spec.Role)
591 s.Require().Nil(pubSubAdmin.Spec.Condition)
592
593 wiMember := &v1beta1.IAMPolicyMember{}
594 s.Require().Eventually(func() bool {
595 err := s.Client.Get(s.ctx, types.NamespacedName{
596 Name: fmt.Sprintf("%s-workload-id", kccResourceName),
597 Namespace: bannerGUID,
598 }, wiMember)
599 return err == nil
600 }, s.timeout, s.tick, "expected kcc workload identity IAMPolicyMember was never found")
601 s.Require().True(isOwnedByBanner(wiMember, bannerGUID), "expected IAMPolicyMember to be owned by Banner")
602 s.Require().Equal(meta.DeletionPolicyAbandon, wiMember.Annotations[meta.DeletionPolicyAnnotation])
603 s.Require().Equal(fmt.Sprintf("serviceAccount:%s.svc.id.goog[cnrm-system/cnrm-controller-manager]", generatedProjID), *wiMember.Spec.Member)
604 s.Require().Equal(v1beta1.SchemeGroupVersion.String(), wiMember.Spec.ResourceRef.APIVersion)
605 s.Require().Equal(v1beta1.IAMServiceAccountGVK.Kind, wiMember.Spec.ResourceRef.Kind)
606 s.Require().Equal(kccResourceName, wiMember.Spec.ResourceRef.Name)
607 s.Require().Equal(roles.WorkloadIdentityUser, wiMember.Spec.Role)
608
609 s.validateEdgeLabels(banner)
610 s.checkInfraStatusDatabaseValue(banner, edgedb.InfraStatusReady)
611 }
612
613 func (s *Suite) TestBannerCreation_UsingWarehouse() {
614 bannerGUID := uuid.New().UUID
615 generatedProjID := fmt.Sprintf("%s-%s", gcpinfra.ProjectIDPrefix, gcpProject.RandAN(29-(len(gcpinfra.ProjectIDPrefix))))
616 banner := &bannerAPI.Banner{
617 ObjectMeta: metav1.ObjectMeta{
618 Name: bannerGUID,
619 },
620 Spec: bannerAPI.BannerSpec{
621 DisplayName: "dev1-banner-use-warehouse",
622 GCP: bannerAPI.GCPConfig{
623 ProjectID: generatedProjID,
624 },
625 BSL: bannerAPI.BSLConfig{
626 EnterpriseUnit: bannerAPI.BSLEnterpriseUnit{
627 ID: uuid.New().UUID,
628 },
629 Organization: bannerAPI.BSLOrganization{
630 Name: "test-org-dev1",
631 },
632 },
633 },
634 }
635
636 s.createBanner(banner)
637 defer s.deleteBanner(banner)
638
639
640 project := &resourceAPI.Project{
641 ObjectMeta: metav1.ObjectMeta{
642 Name: projectName,
643 Namespace: bannerGUID,
644 },
645 }
646 s.Require().Eventually(func() bool {
647 err := s.Client.Get(s.ctx, types.NamespacedName{
648 Name: project.Name,
649 Namespace: project.Namespace,
650 }, project)
651 return err == nil
652 }, s.timeout, s.tick, "expected Project was never found")
653 s.Require().True(isOwnedByBanner(project, bannerGUID), "expected (namespaced) Project to be owned by (cluster scoped) Banner")
654
655 project.Status.Conditions = falsifyResourceReadiness(project.Status.Conditions)
656 project.Status.Number = &TestProjectNumber
657 s.Require().NoError(s.Client.Update(s.ctx, project))
658
659
660 s.Require().Eventually(func() bool {
661 service := &serviceAPI.Service{}
662 err := s.Client.Get(s.ctx, types.NamespacedName{
663 Name: "artifactregistry.googleapis.com",
664 Namespace: bannerGUID,
665 }, service)
666 if errors.IsNotFound(err) {
667 return false
668 }
669 s.Require().True(isOwnedByBanner(service, bannerGUID), "expected Service to be owned by Banner")
670 s.Require().Equal(meta.DeletionPolicyAbandon, service.Annotations[meta.DeletionPolicyAnnotation])
671
672 service.Status.Conditions = falsifyResourceReadiness(service.Status.Conditions)
673 s.Require().NoError(s.Client.Update(s.ctx, service))
674 return true
675 }, s.timeout, s.tick, "not all Services were created (enabled)")
676
677
678 garRepo := ®istryAPI.ArtifactRegistryRepository{}
679 s.Require().Eventually(func() bool {
680 err := s.Client.Get(s.ctx, types.NamespacedName{
681 Name: "warehouse",
682 Namespace: bannerGUID,
683 }, garRepo)
684 return err == nil
685 }, s.timeout, s.tick, "expected ArtifactRegistryRepository was never found")
686 s.Require().Equal(meta.DeletionPolicyAbandon, garRepo.Annotations[meta.DeletionPolicyAnnotation])
687 }
688
689 func (s *Suite) pollForSyncedObject(so *syncedobjectApi.SyncedObject) {
690 s.Require().Eventually(func() bool {
691 err := s.Client.Get(s.ctx, types.NamespacedName{
692 Name: so.Name,
693 Namespace: so.Namespace,
694 }, so)
695 return err == nil
696 }, s.timeout, s.tick, fmt.Sprintf("expected SyncedObject %s/%s was never found", so.Namespace, so.Name))
697 }
698
699 func (s *Suite) TestBannerCreation_WithInventoryPruning() {
700 bannerGUID := uuid.New().UUID
701 generatedProjID := fmt.Sprintf("%s-%s", gcpinfra.ProjectIDPrefix, gcpProject.RandAN(29-(len(gcpinfra.ProjectIDPrefix))))
702 banner := &bannerAPI.Banner{
703 ObjectMeta: metav1.ObjectMeta{
704 Name: bannerGUID,
705 },
706 Spec: bannerAPI.BannerSpec{
707 DisplayName: "test-banner-prune",
708 GCP: bannerAPI.GCPConfig{
709 ProjectID: generatedProjID,
710 },
711 BSL: bannerAPI.BSLConfig{
712 EnterpriseUnit: bannerAPI.BSLEnterpriseUnit{
713 ID: uuid.New().UUID,
714 },
715 Organization: bannerAPI.BSLOrganization{
716 Name: "test-org-dev-2",
717 },
718 },
719 },
720 }
721
722 s.createBanner(banner)
723 defer s.deleteBanner(banner)
724
725
726
727 project := &resourceAPI.Project{
728 ObjectMeta: metav1.ObjectMeta{
729 Name: projectName,
730 Namespace: bannerGUID,
731 },
732 }
733 s.Require().Eventually(func() bool {
734 err := s.Client.Get(s.ctx, types.NamespacedName{
735 Name: project.Name,
736 Namespace: project.Namespace,
737 }, project)
738 return err == nil
739 }, s.timeout, s.tick, "expected Project was never found")
740 s.Require().True(isOwnedByBanner(project, bannerGUID), "expected (namespaced) Project to be owned by (cluster scoped) Banner")
741
742 project.Status.Conditions = falsifyResourceReadiness(project.Status.Conditions)
743 project.Status.Number = &TestProjectNumber
744 s.Require().NoError(s.Client.Update(s.ctx, project))
745 s.Require().Eventually(func() bool {
746 err := s.Client.Get(s.ctx, types.NamespacedName{
747 Name: banner.Name,
748 Namespace: banner.Namespace,
749 }, banner)
750 return err == nil && banner.Status.ProjectRef != ""
751 }, s.timeout, s.tick, "expected Banner Status was never set")
752
753
754 banner.Status.Inventory = &inventory.ResourceInventory{}
755 banner.Status.Inventory.Entries = append(banner.Status.Inventory.Entries, generateMockObjects()...)
756 s.Require().NoError(s.Client.Update(s.ctx, banner))
757
758
759 projectViaStatusRef := &resourceAPI.Project{}
760 s.Require().Eventually(func() bool {
761 err := s.Client.Get(s.ctx, types.NamespacedName{
762 Name: strings.Split(banner.Status.ProjectRef, "/")[1],
763 Namespace: strings.Split(banner.Status.ProjectRef, "/")[0],
764 }, projectViaStatusRef)
765 return err == nil
766 }, s.timeout, s.tick, "expected Project was never found")
767 s.Equal(projectViaStatusRef, project, "could not find Project obj via Banner.Status.ProjectRef")
768
769
770 namespace := &corev1.Namespace{}
771 s.Require().Eventually(func() bool {
772 err := s.Client.Get(s.ctx, types.NamespacedName{
773 Name: bannerGUID,
774 }, namespace)
775 if err != nil {
776 return false
777 }
778 nsAnnos := namespace.Annotations
779 if nsAnnos != nil {
780 return s.Equal(generatedProjID, namespace.Annotations[meta.ProjectAnnotation])
781 }
782 return false
783 }, s.timeout, s.tick, "expected Namespace was never found or didnt have correct annotation")
784
785
786
787
788
789
790
791
792
793
794
795 s.Require().Eventually(func() bool {
796 for _, api := range gcpinfra.TenantAPIs {
797 service := &serviceAPI.Service{}
798 err := s.Client.Get(s.ctx, types.NamespacedName{
799 Name: api,
800 Namespace: bannerGUID,
801 }, service)
802 if errors.IsNotFound(err) {
803 return false
804 }
805 s.Require().True(isOwnedByBanner(service, bannerGUID), "expected Service to be owned by Banner")
806
807
808 service.Status.Conditions = falsifyResourceReadiness(service.Status.Conditions)
809 s.Require().NoError(s.Client.Update(s.ctx, service))
810 }
811 return true
812 }, s.timeout, s.tick, "not all Services were created (enabled)")
813
814
815 sb := &storageAPI.StorageBucket{}
816 s.Require().Eventually(func() bool {
817 err := s.Client.Get(s.ctx, types.NamespacedName{
818 Name: generatedProjID,
819 Namespace: bannerGUID,
820 }, sb)
821 return err == nil
822 }, s.timeout, s.tick, "expected StorageBucket was never found")
823 s.Require().True(isOwnedByBanner(sb, bannerGUID), "expected StorageBucket to be owned by Banner")
824
825
826 if integration.IsIntegrationTest() {
827
828
829 for _, secretID := range edgeconstants.PlatformSecretIDs {
830 v, err := s.sMgr.clients[bannerGUID].GetLatestSecretValue(s.ctx, secretID)
831 s.NoError(err)
832 s.Equal(("mocked secret value"), string(v))
833 }
834 } else {
835
836 s.Require().Eventually(func() bool {
837 c := s.sMgr.clients[generatedProjID]
838 if c == nil {
839 return false
840 }
841 for _, secretID := range edgeconstants.PlatformSecretIDs {
842 v, err := c.GetLatestSecretValue(s.ctx, secretID)
843 if err != nil {
844 return false
845 }
846 if !s.Equal(DefaultSecretValue, string(v)) {
847 return false
848 }
849 }
850 return true
851 }, s.timeout, s.tick, "never got a generated GCP Project ID")
852 }
853
854 s.Require().Eventually(func() bool {
855 err := s.Client.Get(s.ctx, types.NamespacedName{
856 Name: banner.Name,
857 Namespace: banner.Namespace,
858 }, banner)
859 return err == nil && kmeta.IsStatusConditionTrue(banner.Status.Conditions, status.ReadyCondition)
860 }, s.timeout, s.tick, "Banner never became Ready")
861
862
863
864
865
866 hash := uuid.FromUUID(banner.Status.ClusterInfraClusterEdgeID).Hash()
867 kccResourceName := fmt.Sprintf("kcc-%s", hash)
868
869 infraIAMSA := &v1beta1.IAMServiceAccount{}
870 s.Require().Eventually(func() bool {
871
872
873
874
875 err := s.Client.Get(s.ctx, types.NamespacedName{
876 Name: kccResourceName,
877 Namespace: bannerGUID,
878 }, infraIAMSA)
879 return err == nil
880 }, s.timeout, s.tick, "expected kcc IAMServiceAccount was never found")
881 s.Require().True(isOwnedByBanner(infraIAMSA, bannerGUID), "expected IAMServiceAccount to be owned by Banner")
882 s.Require().Equal(fmt.Sprintf("k8s cfg connector for project %s", banner.Spec.DisplayName), *infraIAMSA.Spec.DisplayName)
883
884 projectOwner := &v1beta1.IAMPolicyMember{}
885 s.Require().Eventually(func() bool {
886 err := s.Client.Get(s.ctx, types.NamespacedName{
887 Name: fmt.Sprintf("%s-owner", kccResourceName),
888 Namespace: bannerGUID,
889 }, projectOwner)
890 return err == nil
891 }, s.timeout, s.tick, "expected kcc project owner IAMPolicyMember was never found")
892 s.Require().True(isOwnedByBanner(projectOwner, bannerGUID), "expected IAMPolicyMember to be owned by Banner")
893 s.Require().Equal(fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", kccResourceName, generatedProjID), *projectOwner.Spec.Member)
894 s.Require().Equal(resourceAPI.SchemeGroupVersion.String(), projectOwner.Spec.ResourceRef.APIVersion)
895 s.Require().Equal(resourceAPI.ProjectGVK.Kind, projectOwner.Spec.ResourceRef.Kind)
896 s.Require().Equal(generatedProjID, projectOwner.Spec.ResourceRef.External)
897 s.Require().Equal(roles.Owner, projectOwner.Spec.Role)
898
899 wiMember := &v1beta1.IAMPolicyMember{}
900 s.Require().Eventually(func() bool {
901 err := s.Client.Get(s.ctx, types.NamespacedName{
902 Name: fmt.Sprintf("%s-workload-id", kccResourceName),
903 Namespace: bannerGUID,
904 }, wiMember)
905 return err == nil
906 }, s.timeout, s.tick, "expected kcc workload identity IAMPolicyMember was never found")
907 s.Require().True(isOwnedByBanner(wiMember, bannerGUID), "expected IAMPolicyMember to be owned by Banner")
908 s.Require().Equal(fmt.Sprintf("serviceAccount:%s.svc.id.goog[cnrm-system/cnrm-controller-manager]", generatedProjID), *wiMember.Spec.Member)
909 s.Require().Equal(v1beta1.SchemeGroupVersion.String(), wiMember.Spec.ResourceRef.APIVersion)
910 s.Require().Equal(v1beta1.IAMServiceAccountGVK.Kind, wiMember.Spec.ResourceRef.Kind)
911 s.Require().Equal(kccResourceName, wiMember.Spec.ResourceRef.Name)
912 s.Require().Equal(roles.WorkloadIdentityUser, wiMember.Spec.Role)
913
914 storageMember := &v1beta1.IAMPolicyMember{}
915 s.Require().Eventually(func() bool {
916 err := s.Client.Get(s.ctx, types.NamespacedName{
917 Name: fmt.Sprintf("%s-storage-siemwriter", kccResourceName),
918 Namespace: bannerGUID,
919 }, storageMember)
920 return err == nil
921 }, s.timeout, s.tick, "expected kcc StorageBucket IAMPolicyMember was never found")
922 s.Require().True(isOwnedByBanner(storageMember, bannerGUID), "expected IAMPolicyMember to be owned by Banner")
923 s.Require().Equal(fmt.Sprintf("serviceAccount:service-%s@gcp-sa-logging.iam.gserviceaccount.com", TestProjectNumber), *storageMember.Spec.Member)
924 s.Require().Equal(storageAPI.SchemeGroupVersion.String(), storageMember.Spec.ResourceRef.APIVersion)
925 s.Require().Equal(storageAPI.StorageBucketGVK.Kind, storageMember.Spec.ResourceRef.Kind)
926 s.Require().Equal(fmt.Sprintf("%s-siem", s.projectID), storageMember.Spec.ResourceRef.External)
927 s.Require().Equal(roles.StorageObjectCreator, storageMember.Spec.Role)
928
929
930 s.Eventually(func() bool {
931 err := s.Client.Get(s.ctx, types.NamespacedName{
932 Name: banner.Name,
933 Namespace: banner.Namespace,
934 }, banner)
935 return err == nil && len(banner.Status.Inventory.Entries) == 36
936 }, s.timeout, s.tick, fmt.Sprintf("expected inventory not created %d", len(banner.Status.Inventory.Entries)))
937
938 s.checkInfraStatusDatabaseValue(banner, edgedb.InfraStatusReady)
939 }
940
941
942
943 func generateMockObjects() []inventory.ResourceRef {
944 return []inventory.ResourceRef{
945 {
946 ID: "abc-123-456",
947 Version: "v1",
948 },
949 {
950 ID: "abc-456-789",
951 Version: "v1",
952 },
953 {
954 ID: "abc-789-123",
955 Version: "v1",
956 },
957 }
958 }
959
960 func (s *Suite) TestBannerDeletion_ResourceLifecycle() {
961 integration.SkipIfNot(s.Framework)
962
963 bannerGUID := "guid-abc-123"
964 banner := &bannerAPI.Banner{
965 ObjectMeta: metav1.ObjectMeta{
966 Name: bannerGUID,
967 },
968 Spec: bannerAPI.BannerSpec{
969 DisplayName: "Testy McBanner",
970 BSL: bannerAPI.BSLConfig{
971 Organization: bannerAPI.BSLOrganization{
972 Name: "test-org",
973 },
974 },
975 },
976 }
977 project := &resourceAPI.Project{
978 ObjectMeta: metav1.ObjectMeta{
979 Name: projectName,
980 Namespace: bannerGUID,
981 },
982 }
983 s.Require().Eventually(func() bool {
984 err := s.Client.Get(s.ctx, types.NamespacedName{
985 Name: project.Name,
986 Namespace: project.Namespace,
987 }, project)
988 return err == nil
989 }, s.timeout, s.tick, "expected Project was never found")
990 s.Require().NoError(s.Client.Create(s.ctx, banner))
991 s.Require().NoError(s.Client.Delete(s.ctx, banner))
992 s.Require().Eventually(func() bool {
993 err := s.Client.Get(s.ctx, types.NamespacedName{
994 Name: banner.Name,
995 Namespace: banner.Namespace,
996 }, banner)
997 return errors.IsNotFound(err)
998 }, s.timeout, s.tick, "test Banner was never deleted")
999
1000 s.Require().Eventually(func() bool {
1001 p := &resourceAPI.Project{}
1002 err := s.Client.Get(s.ctx, types.NamespacedName{
1003 Name: project.Name,
1004 Namespace: project.Namespace,
1005 }, p)
1006 return errors.IsNotFound(err)
1007 }, s.timeout, s.tick, "banner Project was never deleted")
1008
1009
1010 }
1011
1012 func (s *Suite) TestBannerCreation_CouchdbConfig() {
1013 bannerGUID := uuid.New().UUID
1014 generatedProjID := fmt.Sprintf("%s-%s", gcpinfra.ProjectIDPrefix, gcpProject.RandAN(29-(len(gcpinfra.ProjectIDPrefix))))
1015 banner := &bannerAPI.Banner{
1016 ObjectMeta: metav1.ObjectMeta{
1017 Name: bannerGUID,
1018 },
1019 Spec: bannerAPI.BannerSpec{
1020 DisplayName: "couchdb-enabled",
1021 GCP: bannerAPI.GCPConfig{
1022 ProjectID: generatedProjID,
1023 },
1024 Enablements: []string{
1025 "couchdb",
1026 },
1027 BSL: bannerAPI.BSLConfig{
1028 EnterpriseUnit: bannerAPI.BSLEnterpriseUnit{
1029 ID: uuid.New().UUID,
1030 },
1031 Organization: bannerAPI.BSLOrganization{
1032 Name: "test-org-dev-4",
1033 },
1034 },
1035 },
1036 }
1037 s.createBanner(banner)
1038 defer s.deleteBanner(banner)
1039
1040
1041
1042 project := &resourceAPI.Project{
1043 ObjectMeta: metav1.ObjectMeta{
1044 Name: projectName,
1045 Namespace: bannerGUID,
1046 },
1047 }
1048 s.Require().Eventually(func() bool {
1049 err := s.Client.Get(s.ctx, types.NamespacedName{
1050 Name: project.Name,
1051 Namespace: project.Namespace,
1052 }, project)
1053 return err == nil
1054 }, s.timeout, s.tick, "expected Project was never found")
1055 s.Require().True(isOwnedByBanner(project, bannerGUID), "expected (namespaced) Project to be owned by (cluster scoped) Banner")
1056 project.Status.Conditions = falsifyResourceReadiness(project.Status.Conditions)
1057 project.Status.Number = &TestProjectNumber
1058 s.Require().NoError(s.Client.Update(s.ctx, project))
1059 s.Require().Eventually(func() bool {
1060 err := s.Client.Get(s.ctx, types.NamespacedName{
1061 Name: banner.Name,
1062 Namespace: banner.Namespace,
1063 }, banner)
1064 return err == nil && banner.Status.ProjectRef != ""
1065 }, s.timeout, s.tick, "expected Banner Status was never set")
1066
1067
1068 s.Require().Eventually(func() bool {
1069 for _, api := range gcpinfra.TenantAPIs {
1070 service := &serviceAPI.Service{}
1071 err := s.Client.Get(s.ctx, types.NamespacedName{
1072 Name: api,
1073 Namespace: bannerGUID,
1074 }, service)
1075 if errors.IsNotFound(err) {
1076 return false
1077 }
1078 s.Require().True(isOwnedByBanner(service, bannerGUID), "expected Service to be owned by Banner")
1079 s.Require().Equal(meta.DeletionPolicyAbandon, service.Annotations[meta.DeletionPolicyAnnotation])
1080
1081
1082 service.Status.Conditions = falsifyResourceReadiness(service.Status.Conditions)
1083 s.Require().NoError(s.Client.Update(s.ctx, service))
1084 }
1085 return true
1086 }, s.timeout, s.tick, "not all Services were created (enabled)")
1087
1088
1089 s.Require().Eventually(func() bool {
1090 err := s.Client.Get(s.ctx, types.NamespacedName{
1091 Name: banner.Name,
1092 Namespace: banner.Namespace,
1093 }, banner)
1094 return err == nil && kmeta.IsStatusConditionTrue(banner.Status.Conditions, status.ReadyCondition)
1095 }, s.timeout, s.tick, "Banner never became Ready")
1096
1097 s.checkInfraStatusDatabaseValue(banner, edgedb.InfraStatusReady)
1098 }
1099
1100 func (s *Suite) TestBannerControllerSetsInfraStatusError() {
1101 bannerGUID := uuid.New().UUID
1102 testBSLID := uuid.New().UUID
1103 testOrg := uuid.New().UUID
1104
1105 banner := &bannerAPI.Banner{
1106 ObjectMeta: metav1.ObjectMeta{
1107 Name: bannerGUID,
1108 },
1109 Spec: bannerAPI.BannerSpec{
1110 DisplayName: "test-infra-status-error",
1111 GCP: bannerAPI.GCPConfig{
1112 ProjectID: "invalid projectID",
1113 },
1114 BSL: bannerAPI.BSLConfig{
1115 EnterpriseUnit: bannerAPI.BSLEnterpriseUnit{
1116 ID: testBSLID,
1117 },
1118 Organization: bannerAPI.BSLOrganization{
1119 Name: testOrg,
1120 },
1121 },
1122 },
1123 }
1124
1125 s.createBanner(banner)
1126 defer s.deleteBanner(banner)
1127
1128
1129 s.checkInfraStatusDatabaseValue(banner, edgedb.InfraStatusError)
1130 }
1131
1132 func (s *Suite) createBanner(banner *bannerAPI.Banner) {
1133 bannerType := string(bannerconstants.EU)
1134 tenantID := uuid.New().UUID
1135 s.Require().NoError(banner.IsValid())
1136 _, err := s.db.Exec("INSERT INTO tenants (tenant_edge_id, org_id, org_name) VALUES ($1, $2, $3);", tenantID, banner.Spec.BSL.EnterpriseUnit.ID, banner.Spec.BSL.Organization.Name)
1137 s.NoError(err)
1138 const stmt = "INSERT INTO banners (banner_bsl_id, banner_name, banner_type, project_id, tenant_edge_id, banner_edge_id, description) VALUES ($1, $2, $3, $4, $5, $6, $7)"
1139 _, err = s.db.Exec(stmt, banner.Spec.BSL.EnterpriseUnit.ID, banner.Spec.DisplayName, bannerType, banner.Spec.GCP.ProjectID, tenantID, banner.Name, nil)
1140 s.Require().NoError(err)
1141 s.Require().NoError(s.Client.Create(s.ctx, banner))
1142 }
1143
1144 func (s *Suite) checkInfraStatusDatabaseValue(banner *bannerAPI.Banner, infraStatus edgedb.InfraStatus) {
1145 const stmt = "SELECT infra_status FROM banners WHERE banner_edge_id=$1"
1146 s.Require().Eventually(func() bool {
1147 var v string
1148 err := s.db.QueryRow(stmt, banner.Name).Scan(&v)
1149 if err != nil {
1150 return false
1151 }
1152 return v == string(infraStatus)
1153 }, s.timeout, s.tick, "expected infra_status value %q not set in database", infraStatus)
1154 }
1155
1156 func (s *Suite) validateEdgeLabels(banner *bannerAPI.Banner) {
1157 const stmt = "SELECT label_key, visible, editable, color, description, label_type FROM labels WHERE banner_edge_id=$1 AND label_type=$2 AND label_key=$3"
1158
1159 expectedEdgeLabels := capabilities.EdgeAutomatedCapabilityLabels
1160 expectedEdgeLabels = append(expectedEdgeLabels, fetchEdgeCapabilityLabels()...)
1161 for _, label := range expectedEdgeLabels {
1162 var actualLabel = &model.LabelInput{}
1163 s.Require().Eventually(func() bool {
1164 row := s.db.QueryRowContext(s.ctx, stmt, banner.Name, label.Type, label.Key)
1165 if err := row.Scan(&actualLabel.Key, &actualLabel.Visible, &actualLabel.Editable, &actualLabel.Color, &actualLabel.Description, &actualLabel.Type); err != nil {
1166 return false
1167 }
1168
1169 actualLabel.BannerEdgeID = banner.GetName()
1170 label.BannerEdgeID = banner.GetName()
1171 return cmp.Equal(label, actualLabel)
1172 }, s.timeout, s.tick, "expected label", label, "actual label", actualLabel)
1173 }
1174 }
1175
1176 func (s *Suite) deleteBanner(banner *bannerAPI.Banner) {
1177 s.NoError(s.Client.Delete(s.ctx, banner))
1178 s.Eventually(func() bool {
1179 return errors.IsNotFound(s.Client.Get(s.ctx, client.ObjectKeyFromObject(banner), banner))
1180 }, s.timeout, s.tick, "banner resource was never deleted %v", banner)
1181 }
1182
View as plain text