Skip to content

Commit 1fb3880

Browse files
feat: Add basic WSA loading (#4)
* Add unit test for validation * Remove unused webhook verbs and defaulting webhook config * Add listing of WSAs * Add E2E test for WSA listing
1 parent 3ce644a commit 1fb3880

File tree

8 files changed

+109
-77
lines changed

8 files changed

+109
-77
lines changed

PROJECT

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ resources:
1818
path: github.com/octopusdeploy/octopus-permissions-controller/api/v1beta1
1919
version: v1beta1
2020
webhooks:
21-
defaulting: true
2221
validation: true
2322
webhookVersion: v1
2423
version: "3"

config/webhook/kustomizeconfig.yaml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,11 @@ nameReference:
44
- kind: Service
55
version: v1
66
fieldSpecs:
7-
- kind: MutatingWebhookConfiguration
8-
group: admissionregistration.k8s.io
9-
path: webhooks/clientConfig/service/name
107
- kind: ValidatingWebhookConfiguration
118
group: admissionregistration.k8s.io
129
path: webhooks/clientConfig/service/name
1310

1411
namespace:
15-
- kind: MutatingWebhookConfiguration
16-
group: admissionregistration.k8s.io
17-
path: webhooks/clientConfig/service/namespace
18-
create: true
1912
- kind: ValidatingWebhookConfiguration
2013
group: admissionregistration.k8s.io
2114
path: webhooks/clientConfig/service/namespace

config/webhook/manifests.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ webhooks:
2020
- v1beta1
2121
operations:
2222
- CREATE
23-
- UPDATE
2423
resources:
2524
- workloadserviceaccounts
2625
sideEffects: None

internal/controller/workloadserviceaccount_controller.go

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -39,38 +39,35 @@ type WorkloadServiceAccountReconciler struct {
3939

4040
// Reconcile is part of the main kubernetes reconciliation loop which aims to
4141
// move the current state of the cluster closer to the desired state.
42-
// TODO(user): Modify the Reconcile function to compare the state specified by
43-
// the WorkloadServiceAccount object against the actual cluster state, and then
44-
// perform operations to make the cluster state reflect the state specified by
45-
// the user.
46-
//
47-
// For more details, check Reconcile and its Result here:
48-
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile
4942
func (r *WorkloadServiceAccountReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
5043
log := logf.FromContext(ctx)
5144

52-
var wsa agentoctopuscomv1beta1.WorkloadServiceAccount
53-
if err := r.Get(ctx, req.NamespacedName, &wsa); err != nil {
54-
log.Error(err, "unable to fetch WorkloadServiceAccount")
55-
return ctrl.Result{}, client.IgnoreNotFound(err)
56-
}
57-
58-
for _, project := range wsa.Spec.Scope.Projects {
59-
log.Info("We have a project scope", "project", project)
60-
}
61-
62-
for _, environment := range wsa.Spec.Scope.Environments {
63-
log.Info("We have an environment scope", "project", environment)
64-
}
45+
log.Info("WorkloadServiceAccount reconciliation triggered")
6546

66-
for _, tenant := range wsa.Spec.Scope.Tenants {
67-
log.Info("We have a tenant scope", "tenant", tenant)
47+
wsaList := &agentoctopuscomv1beta1.WorkloadServiceAccountList{}
48+
if err := r.List(ctx, wsaList, client.InNamespace(req.Namespace)); err != nil {
49+
log.Error(err, "failed to list WorkloadServiceAccounts")
50+
return ctrl.Result{}, err
6851
}
6952

70-
for _, step := range wsa.Spec.Scope.Steps {
71-
log.Info("We have a step scope", "step", step)
53+
log.Info("Found WSAs in namespace", "count", len(wsaList.Items))
54+
55+
for _, currentWSA := range wsaList.Items {
56+
for _, project := range currentWSA.Spec.Scope.Projects {
57+
log.Info("WSA has project scope", "wsa", currentWSA.Name, "project", project)
58+
}
59+
for _, environment := range currentWSA.Spec.Scope.Environments {
60+
log.Info("WSA has environment scope", "wsa", currentWSA.Name, "environment", environment)
61+
}
62+
for _, tenant := range currentWSA.Spec.Scope.Tenants {
63+
log.Info("WSA has tenant scope", "wsa", currentWSA.Name, "tenant", tenant)
64+
}
65+
for _, step := range currentWSA.Spec.Scope.Steps {
66+
log.Info("WSA has step scope", "wsa", currentWSA.Name, "step", step)
67+
}
7268
}
7369

70+
log.Info("Successfully reconciled WorkloadServiceAccounts")
7471
return ctrl.Result{}, nil
7572
}
7673

internal/webhook/v1beta1/workloadserviceaccount_webhook.go

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ func SetupWorkloadServiceAccountWebhookWithManager(mgr ctrl.Manager) error {
4545
// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
4646
// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here.
4747
// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook.
48-
// +kubebuilder:webhook:path=/validate-agent-octopus-com-v1beta1-workloadserviceaccount,mutating=false,failurePolicy=fail,sideEffects=None,groups=agent.octopus.com,resources=workloadserviceaccounts,verbs=create;update,versions=v1beta1,name=vworkloadserviceaccount-v1beta1.kb.io,admissionReviewVersions=v1
48+
// +kubebuilder:webhook:path=/validate-agent-octopus-com-v1beta1-workloadserviceaccount,mutating=false,failurePolicy=fail,sideEffects=None,groups=agent.octopus.com,resources=workloadserviceaccounts,verbs=create,versions=v1beta1,name=vworkloadserviceaccount-v1beta1.kb.io,admissionReviewVersions=v1
4949

5050
// WorkloadServiceAccountCustomValidator struct is responsible for validating the WorkloadServiceAccount resource
51-
// when it is created, updated, or deleted.
51+
// when it is created.
5252
//
5353
// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,
5454
// as this struct is used only for temporary operations and does not need to be deeply copied.
@@ -74,28 +74,12 @@ func (v *WorkloadServiceAccountCustomValidator) ValidateCreate(_ context.Context
7474
return nil, nil
7575
}
7676

77-
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type WorkloadServiceAccount.
78-
func (v *WorkloadServiceAccountCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
79-
workloadserviceaccount, ok := newObj.(*agentoctopuscomv1beta1.WorkloadServiceAccount)
80-
if !ok {
81-
return nil, fmt.Errorf("expected a WorkloadServiceAccount object for the newObj but got %T", newObj)
82-
}
83-
workloadserviceaccountlog.Info("Validation for WorkloadServiceAccount upon update", "name", workloadserviceaccount.GetName())
84-
85-
// TODO(user): fill in your validation logic upon object update.
86-
77+
// ValidateUpdate implements webhook.CustomValidator but is not used since webhook only handles create operations.
78+
func (v *WorkloadServiceAccountCustomValidator) ValidateUpdate(_ context.Context, _, _ runtime.Object) (admission.Warnings, error) {
8779
return nil, nil
8880
}
8981

90-
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type WorkloadServiceAccount.
91-
func (v *WorkloadServiceAccountCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
92-
workloadserviceaccount, ok := obj.(*agentoctopuscomv1beta1.WorkloadServiceAccount)
93-
if !ok {
94-
return nil, fmt.Errorf("expected a WorkloadServiceAccount object but got %T", obj)
95-
}
96-
workloadserviceaccountlog.Info("Validation for WorkloadServiceAccount upon deletion", "name", workloadserviceaccount.GetName())
97-
98-
// TODO(user): fill in your validation logic upon object deletion.
99-
82+
// ValidateDelete implements webhook.CustomValidator but is not used since webhook only handles create operations.
83+
func (v *WorkloadServiceAccountCustomValidator) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
10084
return nil, nil
10185
}

internal/webhook/v1beta1/workloadserviceaccount_webhook_test.go

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,55 +17,58 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
20+
"context"
21+
2022
. "github.com/onsi/ginkgo/v2"
2123
. "github.com/onsi/gomega"
2224

2325
agentoctopuscomv1beta1 "github.com/octopusdeploy/octopus-permissions-controller/api/v1beta1"
24-
// TODO (user): Add any additional imports if needed
2526
)
2627

2728
var _ = Describe("WorkloadServiceAccount Webhook", func() {
2829
var (
2930
obj *agentoctopuscomv1beta1.WorkloadServiceAccount
3031
oldObj *agentoctopuscomv1beta1.WorkloadServiceAccount
3132
validator WorkloadServiceAccountCustomValidator
33+
ctx context.Context
3234
)
3335

3436
BeforeEach(func() {
3537
obj = &agentoctopuscomv1beta1.WorkloadServiceAccount{}
3638
oldObj = &agentoctopuscomv1beta1.WorkloadServiceAccount{}
3739
validator = WorkloadServiceAccountCustomValidator{}
40+
ctx = context.Background()
3841
Expect(validator).NotTo(BeNil(), "Expected validator to be initialized")
3942
Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized")
4043
Expect(obj).NotTo(BeNil(), "Expected obj to be initialized")
41-
// TODO (user): Add any setup logic common to all tests
4244
})
4345

4446
AfterEach(func() {
4547
// TODO (user): Add any teardown logic common to all tests
4648
})
4749

4850
Context("When creating or updating WorkloadServiceAccount under Validating Webhook", func() {
49-
// TODO (user): Add logic for validating webhooks
50-
// Example:
51-
// It("Should deny creation if a required field is missing", func() {
52-
// By("simulating an invalid creation scenario")
53-
// obj.SomeRequiredField = ""
54-
// Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())
55-
// })
56-
//
57-
// It("Should admit creation if all required fields are present", func() {
58-
// By("simulating an invalid creation scenario")
59-
// obj.SomeRequiredField = "valid_value"
60-
// Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())
61-
// })
62-
//
63-
// It("Should validate updates correctly", func() {
64-
// By("simulating a valid update scenario")
65-
// oldObj.SomeRequiredField = "updated_value"
66-
// obj.SomeRequiredField = "updated_value"
67-
// Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())
68-
// })
51+
It("Should allow creation with valid scope", func() {
52+
By("setting up a WorkloadServiceAccount with project scope")
53+
obj.Spec.Scope.Projects = []string{"web-app", "api-service"}
54+
55+
By("validating the creation")
56+
warnings, err := validator.ValidateCreate(ctx, obj)
57+
Expect(err).NotTo(HaveOccurred())
58+
Expect(warnings).To(BeNil())
59+
})
60+
61+
It("Should deny creation if all scopes are missing", func() {
62+
By("setting up a WorkloadServiceAccount with no scopes")
63+
obj.Spec.Scope = agentoctopuscomv1beta1.WorkloadServiceAccountScope{}
64+
65+
By("validating the creation should fail")
66+
warnings, err := validator.ValidateCreate(ctx, obj)
67+
Expect(err).To(HaveOccurred())
68+
Expect(err.Error()).To(ContainSubstring("at least one scope must be defined"))
69+
Expect(warnings).To(BeNil())
70+
})
71+
6972
})
7073

7174
})

test/e2e/e2e_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,50 @@ var _ = Describe("Manager", Ordered, func() {
283283

284284
// +kubebuilder:scaffold:e2e-webhooks-checks
285285

286+
It("should list WSAs when creating a resource", func() {
287+
By("creating a WorkloadServiceAccount resource")
288+
cmd := exec.Command("kubectl", "apply", "-f", "test/e2e/test-wsa.yaml", "-n", namespace)
289+
_, err := utils.Run(cmd)
290+
Expect(err).NotTo(HaveOccurred(), "Failed to create WSA resource")
291+
292+
By("waiting for reconciliation to process the WSA")
293+
Eventually(func(g Gomega) {
294+
cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace)
295+
output, err := utils.Run(cmd)
296+
g.Expect(err).NotTo(HaveOccurred())
297+
g.Expect(output).To(ContainSubstring("Found WSAs in namespace"))
298+
g.Expect(output).To(ContainSubstring("WSA has project scope"))
299+
g.Expect(output).NotTo(ContainSubstring("failed to list WorkloadServiceAccounts"))
300+
}).Should(Succeed())
301+
302+
By("cleaning up the WSA resource")
303+
cmd = exec.Command("kubectl", "delete", "-f", "test/e2e/test-wsa.yaml", "-n", namespace)
304+
_, _ = utils.Run(cmd)
305+
})
306+
307+
It("should list WSAs when deleting a resource", func() {
308+
By("creating a WorkloadServiceAccount resource")
309+
cmd := exec.Command("kubectl", "apply", "-f", "test/e2e/test-wsa.yaml", "-n", namespace)
310+
_, err := utils.Run(cmd)
311+
Expect(err).NotTo(HaveOccurred(), "Failed to create WSA resource")
312+
313+
By("waiting for initial reconciliation")
314+
time.Sleep(2 * time.Second)
315+
316+
By("deleting the WorkloadServiceAccount resource")
317+
cmd = exec.Command("kubectl", "delete", "-f", "test/e2e/test-wsa.yaml", "-n", namespace)
318+
_, err = utils.Run(cmd)
319+
Expect(err).NotTo(HaveOccurred(), "Failed to delete WSA resource")
320+
321+
By("verifying reconciliation handles deletion without listing errors")
322+
Eventually(func(g Gomega) {
323+
cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace, "--tail=50")
324+
output, err := utils.Run(cmd)
325+
g.Expect(err).NotTo(HaveOccurred())
326+
g.Expect(output).NotTo(ContainSubstring("failed to list WorkloadServiceAccounts"))
327+
}).Should(Succeed())
328+
})
329+
286330
// TODO: Customize the e2e test suite with scenarios specific to your project.
287331
// Consider applying sample/CR(s) and check their status and/or verifying
288332
// the reconciliation by using the metrics, i.e.:

test/e2e/test-wsa.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
apiVersion: agent.octopus.com/v1beta1
2+
kind: WorkloadServiceAccount
3+
metadata:
4+
name: test-wsa
5+
spec:
6+
scope:
7+
projects: ["test-project"]
8+
environments: ["test-env"]
9+
permissions:
10+
permissions:
11+
- apiGroups: [""]
12+
resources: ["pods"]
13+
verbs: ["get", "list"]

0 commit comments

Comments
 (0)