Skip to content

Commit 04948b1

Browse files
Merge pull request #43 from alphagov/controller-approval-job-request-logic
Added approval logic and tests
2 parents a4fe418 + 96f8018 commit 04948b1

6 files changed

Lines changed: 360 additions & 55 deletions

File tree

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ brew install k3d
1313
```
1414

1515
### Custom Resource Definitions (CRDs)
16-
1716
#### JobRequest
1817

1918
A `JobRequest` represents a request to run a command/job.
@@ -50,12 +49,14 @@ apiVersion: platform.publishing.service.gov.uk/v1
5049
kind: JobRequestReview
5150
metadata:
5251
name: something-approval
52+
annotations:
53+
platform.publishing.service.gov.uk/reviewed-by: arn:aws:sts::123456789:assumed-role/otheruser.name-platformengineer/environment-platformengineer
5354
spec:
5455
jobRequestName: something
5556
decision: Approved
5657
description: "LGTM"
5758
status:
58-
reviewedBy: arn:aws:sts::123456789:assumed-role/otheruser.name-platformengineer/environment-platformengineer
59+
state: Approved
5960
```
6061

6162
### Create and generate the manifests

api/v1/jobrequestreview_types.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ type JobRequestReviewSpec struct {
3333

3434
// JobRequestReviewStatus defines the observed state of JobRequestReview.
3535
type JobRequestReviewStatus struct {
36-
// Kubernetes username of the reviewer.
37-
ReviewedBy string `json:"reviewedBy,omitempty"`
36+
// +kubebuilder:validation:Enum=Approved;Rejected;JobRequestMalformed;JobRequestNotFound
37+
State string `json:"state,omitempty"`
3838
}
3939

4040
// +kubebuilder:object:root=true

cmd/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ func main() {
190190
if err := (&controller.JobRequestReviewReconciler{
191191
Client: mgr.GetClient(),
192192
Scheme: mgr.GetScheme(),
193-
}).SetupWithManager(mgr); err != nil {
193+
}).SetupControllerWithManager(mgr); err != nil {
194194
setupLog.Error(err, "Failed to create controller", "controller", "jobrequestreview")
195195
os.Exit(1)
196196
}

config/crd/bases/platform.publishing.service.gov.uk_jobrequestreviews.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,12 @@ spec:
7474
status:
7575
description: status defines the observed state of JobRequestReview
7676
properties:
77-
reviewedBy:
78-
description: Kubernetes username of the reviewer.
77+
state:
78+
enum:
79+
- Approved
80+
- Rejected
81+
- JobRequestMalformed
82+
- JobRequestNotFound
7983
type: string
8084
type: object
8185
required:

internal/controller/jobrequestreview_controller.go

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
logf "sigs.k8s.io/controller-runtime/pkg/log"
2828

2929
platformv1 "github.com/alphagov/govuk-job-request-operator/api/v1"
30+
"github.com/go-logr/logr"
3031
)
3132

3233
// JobRequestReviewReconciler reconciles a JobRequestReview object
@@ -58,43 +59,71 @@ func (r *JobRequestReviewReconciler) Reconcile(ctx context.Context, req ctrl.Req
5859
}
5960

6061
// Attempt to retrieve JobRequest object and if not stop reconcile and add something to the CRD to indicate a failure
61-
jobRequest := &platformv1.JobRequest{}
62-
// Compose namespace with jobRequest.Name instead of req.NamespacedName
63-
err = r.Get(ctx, req.NamespacedName, jobRequest)
64-
if err != nil {
65-
if apierrors.IsNotFound(err) {
66-
// If the custom resource is not found then it usually means that it was deleted or not created
67-
// In this way, we will stop the reconciliation
68-
log.Error(err, "JobRequest resource not found. Ignoring since object must be deleted")
69-
return ctrl.Result{}, nil
70-
}
71-
// Error reading the object
72-
log.Error(err, "Failed to get JobRequest")
73-
return ctrl.Result{}, nil
62+
resourceResult, jobRequestList := r.getJobRequest(ctx, log, jobRequestReview)
63+
if resourceResult != nil {
64+
return *resourceResult, nil
7465
}
7566

67+
jobRequest := &jobRequestList.Items[0]
68+
7669
// If state is nil then reschedule as jobRequest object isn't setup yet
7770
if jobRequest.Status.State == "" {
71+
log.Info("JobRequest hasn't finished creating so requeueing the reconcile")
7872
// Requeue the reconcile after certain time
7973
return ctrl.Result{RequeueAfter: time.Minute}, nil
8074
}
8175

82-
// If state is failed then stop reconcile and add something to the JobRequestReview to indicate a failure
83-
if jobRequest.Status.State == "Failed" {
84-
// Create an error and log it
85-
log.Error(err, "JobRequest is in a Failed state so can't approve")
76+
// If JobRequest is in Malformed state then set JobRequestReview as Malformed
77+
if jobRequest.Status.State == "Malformed" {
78+
log.Error(err, "JobRequest is in a Malformed state so can't approve")
79+
jobRequestReview.Status.State = "JobRequestMalformed"
80+
err := r.Status().Update(ctx, jobRequestReview)
81+
if err != nil {
82+
log.Error(err, "Failed to update status of JobRequestReview", "errored_obj", jobRequestReview)
83+
}
8684
return ctrl.Result{}, nil
8785
}
8886

87+
// Set JobRequest status and review name and update the status
88+
jobRequestReview.Status.State = jobRequestReview.Spec.Decision
8989
jobRequest.Status.State = jobRequestReview.Spec.Decision
90-
// Do we need to recover the object again before flushing the state?
91-
r.Status().Update(ctx, jobRequest)
90+
jobRequest.Status.ReviewName = jobRequestReview.GetName()
91+
err = r.Status().Update(ctx, jobRequest)
92+
if err != nil {
93+
log.Error(err, "Failed to update status of JobRequest", "errored_obj", jobRequest)
94+
}
95+
96+
err = r.Status().Update(ctx, jobRequestReview)
97+
if err != nil {
98+
log.Error(err, "Failed to update status of JobRequestReview", "errored_obj", jobRequest)
99+
}
92100

93101
return ctrl.Result{}, nil
94102
}
95103

104+
func (r *JobRequestReviewReconciler) getJobRequest(ctx context.Context, log logr.Logger, jobRequestReview *platformv1.JobRequestReview) (*ctrl.Result, *platformv1.JobRequestList) {
105+
jobRequestlist := &platformv1.JobRequestList{}
106+
opts := []client.ListOption{
107+
client.MatchingFields{"metadata.name": jobRequestReview.GetName()},
108+
}
109+
110+
// Retrieve the corresponding JobRequest object
111+
if err := r.List(ctx, jobRequestlist, opts...); err != nil || len(jobRequestlist.Items) == 0 {
112+
jobRequestReview.Status.State = "JobRequestNotFound"
113+
err := r.Status().Update(ctx, jobRequestReview)
114+
if err != nil {
115+
log.Error(err, "Failed to UPDATE JobRequestReview resource", "errored_obj", jobRequestReview)
116+
}
117+
118+
log.Error(err, "Failed to retrieve JobRequest")
119+
return &ctrl.Result{}, nil
120+
}
121+
122+
return nil, jobRequestlist
123+
}
124+
96125
// SetupWithManager sets up the controller with the Manager.
97-
func (r *JobRequestReviewReconciler) SetupWithManager(mgr ctrl.Manager) error {
126+
func (r *JobRequestReviewReconciler) SetupControllerWithManager(mgr ctrl.Manager) error {
98127
return ctrl.NewControllerManagedBy(mgr).
99128
For(&platformv1.JobRequestReview{}).
100129
Named("jobrequestreview").

0 commit comments

Comments
 (0)