Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make ScopeTemplate CRs communicate state #20

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
507accf
make ScopeTemplate CRs communicate state
everettraven Sep 7, 2022
b1a3f89
updates to be more similar to PR #21
everettraven Sep 12, 2022
136c9ab
requeue if error in setting status
everettraven Sep 12, 2022
add8cc0
update scopeinstance_controller.go
everettraven Sep 16, 2022
4a9f89e
update list options for delete in scopeinstance controller
everettraven Sep 16, 2022
18f3710
update controllers/scopetemplate_controller.go
everettraven Sep 16, 2022
1063a29
remove unecessary lines
everettraven Sep 16, 2022
9832f87
ignore not found error
everettraven Sep 16, 2022
e99a979
updates based on PR review
everettraven Sep 20, 2022
f80fcf2
update patch config
everettraven Sep 20, 2022
358d4b6
Merge branch 'main' into chore/refactor-controllers
everettraven Sep 21, 2022
19ff799
shared hash
everettraven Sep 21, 2022
6eeb5d8
remove unnecessary comments
everettraven Sep 21, 2022
8cbe23c
fix lint errors
everettraven Sep 21, 2022
ac6787c
fix unit tests
everettraven Sep 21, 2022
18514fd
update todo in mapToScopeTemplate
everettraven Sep 21, 2022
a6a04ce
make verify
everettraven Sep 21, 2022
7c180d6
update hashing method in scopeinstance controller
everettraven Sep 26, 2022
0d806dd
first round of PR review fixes
everettraven Sep 26, 2022
77b7c6f
second round of PR review updates
everettraven Sep 27, 2022
5d6a4f9
updated missed log line to be debug level
everettraven Sep 27, 2022
b8d6c2d
update log message
everettraven Sep 27, 2022
9db48c1
Merge branch 'main' into feature/scopetemplate-state
everettraven Sep 28, 2022
d6082be
Merge branch 'chore/refactor-controllers' into feature/scopetemplate-…
everettraven Sep 28, 2022
aabe667
update method of setting scopetemplate status
everettraven Sep 28, 2022
686918b
Merge branch 'main' into feature/scopetemplate-state
everettraven Oct 4, 2022
b983635
update scopetemplate_controller_test.go
everettraven Oct 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions api/v1/scopetemplate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ type ClusterRoleTemplate struct {
type ScopeTemplateStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file

// Conditions represent the latest available observations of an object's state
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
}

//+kubebuilder:object:root=true
Expand Down
10 changes: 9 additions & 1 deletion api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,77 @@ spec:
type: object
status:
description: ScopeTemplateStatus defines the observed state of ScopeTemplate
properties:
conditions:
description: Conditions represent the latest available observations
of an object's state
items:
description: "Condition contains details for one aspect of the current
state of this API Resource. --- This struct is intended for direct
use as an array at the field path .status.conditions. For example,
type FooStatus struct{ // Represents the observations of a foo's
current state. // Known .status.conditions.type are: \"Available\",
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
// +listType=map // +listMapKey=type Conditions []metav1.Condition
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
type: object
type: object
served: true
Expand Down
62 changes: 61 additions & 1 deletion controllers/scopetemplate_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/sirupsen/logrus"
rbacv1 "k8s.io/api/rbac/v1"
k8sapierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -79,7 +80,16 @@ func (r *ScopeTemplateReconciler) Reconcile(ctx context.Context, req ctrl.Reques

scopeinstances := operatorsv1.ScopeInstanceList{}

if err := r.Client.List(ctx, &scopeinstances, client.InNamespace(st.Namespace)); err != nil {
if err := r.Client.List(ctx, &scopeinstances, &client.ListOptions{}); err != nil {
r.updateScopeTemplateCondition(ctx, st, metav1.Condition{
Type: "Succeeded",
Status: metav1.ConditionFalse,
ObservedGeneration: st.Generation,
LastTransitionTime: metav1.Now(),
Reason: "ScopeInstanceListFailure",
Message: fmt.Sprintf("failed to list ScopeInstances: %s", err),
})

return ctrl.Result{}, err
}

Expand All @@ -96,12 +106,28 @@ func (r *ScopeTemplateReconciler) Reconcile(ctx context.Context, req ctrl.Reques
// create ClusterRoles based on the ScopeTemplate
log.Log.Info("ScopeInstance found that references ScopeTemplate", "name", st.Name)
if err := r.ensureClusterRoles(ctx, st); err != nil {
r.updateScopeTemplateCondition(ctx, st, metav1.Condition{
Type: "Succeeded",
Status: metav1.ConditionFalse,
ObservedGeneration: st.Generation,
LastTransitionTime: metav1.Now(),
Reason: "ClusterRoleCreateFailure",
Message: fmt.Sprintf("failed to create ClusterRoles: %s", err),
})
return ctrl.Result{}, fmt.Errorf("Error in create ClusterRoles: %v", err)
}

// Add requirement to delete old hashes
requirement, err := labels.NewRequirement(scopeTemplateHashKey, selection.NotEquals, []string{util.HashObject(st.Spec)})
if err != nil {
r.updateScopeTemplateCondition(ctx, st, metav1.Condition{
Type: "Succeeded",
Status: metav1.ConditionFalse,
ObservedGeneration: st.Generation,
LastTransitionTime: metav1.Now(),
Reason: "DeleteOldHashRequirementFailure",
Message: fmt.Sprintf("failed to add a requirement to delete old hashes: %s", err),
})
return ctrl.Result{}, err
}
listOptions = append(listOptions, &client.ListOptions{
Expand All @@ -111,11 +137,28 @@ func (r *ScopeTemplateReconciler) Reconcile(ctx context.Context, req ctrl.Reques
}

if err := r.deleteClusterRoles(ctx, listOptions...); err != nil {
r.updateScopeTemplateCondition(ctx, st, metav1.Condition{
Type: "Succeeded",
Status: metav1.ConditionFalse,
ObservedGeneration: st.Generation,
LastTransitionTime: metav1.Now(),
Reason: "DeleteClusterRolesFailure",
Message: fmt.Sprintf("failed to delete ClusterRoles: %s", err),
})
return ctrl.Result{}, err
}

log.Log.Info("No ScopeTemplate error")

r.updateScopeTemplateCondition(ctx, st, metav1.Condition{
Type: "Succeeded",
Status: metav1.ConditionTrue,
ObservedGeneration: st.Generation,
LastTransitionTime: metav1.Now(),
Reason: "ScopeTemplateReconcileSuccess",
Message: "ScopeTemplate successfully reconciled",
})

return ctrl.Result{}, nil
}

Expand Down Expand Up @@ -230,3 +273,20 @@ func (r *ScopeTemplateReconciler) deleteClusterRoles(ctx context.Context, listOp
}
return nil
}

func (r *ScopeTemplateReconciler) updateScopeTemplateCondition(ctx context.Context, st *operatorsv1.ScopeTemplate, condition metav1.Condition) {
// Get the latest instance of the ScopeTemplate in case it has been updated
tempSt := &operatorsv1.ScopeTemplate{}
err := r.Client.Get(ctx, client.ObjectKeyFromObject(st), tempSt)
if err != nil {
log.Log.Error(err, "failed to get the latest version of the ScopeTemplate to update the ScopeTemplate status")
return
}
everettraven marked this conversation as resolved.
Show resolved Hide resolved
// Update the condition of the ScopeTemplate
meta.SetStatusCondition(&tempSt.Status.Conditions, condition)

condErr := r.Status().Update(ctx, tempSt, &client.UpdateOptions{})
if condErr != nil {
log.Log.Error(condErr, "failed to update .Status.Condition of ScopeTemplate", "Condition", condition)
}
}
47 changes: 38 additions & 9 deletions controllers/scopetemplate_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand All @@ -40,9 +41,12 @@ var _ = Describe("ScopeTemplate", func() {
namespace2 *corev1.Namespace
scopeTemplate *operatorsv1.ScopeTemplate
scopeInstance *operatorsv1.ScopeInstance
// Use this to track the test iteration
iteration int
)

BeforeEach(func() {
iteration += 1
namespace = &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-",
Expand All @@ -68,7 +72,9 @@ var _ = Describe("ScopeTemplate", func() {
Spec: operatorsv1.ScopeTemplateSpec{
ClusterRoles: []operatorsv1.ClusterRoleTemplate{
{
GenerateName: "test",
// Create a new ClusterRole for each test iteration so that there are no conflicts with a ClusterRole already existing.
// We can deliberately test this scenario in specific tests in the future.
GenerateName: fmt.Sprintf("test-%d", iteration),
Rules: []rbacv1.PolicyRule{
{
APIGroups: []string{
Expand Down Expand Up @@ -106,6 +112,7 @@ var _ = Describe("ScopeTemplate", func() {

clusterRoleList := listClusterRole(0, labels)
Expect(clusterRoleList.Items).Should(BeNil())
verifyScopeTemplateStatus(scopeTemplate, "Succeeded", "ScopeTemplateReconcileSuccess", metav1.ConditionTrue, "ScopeTemplate successfully reconciled")
})

When("a scopeInstance is created that references the scopeTemplate with a single namespace", func() {
Expand Down Expand Up @@ -154,18 +161,20 @@ var _ = Describe("ScopeTemplate", func() {
Resources: []string{"secrets"},
},
}))
verifyScopeTemplateStatus(scopeTemplate, "Succeeded", "ScopeTemplateReconcileSuccess", metav1.ConditionTrue, "ScopeTemplate successfully reconciled")
})

It("should create the expected RoleBinding within the test namespace", func() {
labels := map[string]string{scopeInstanceUIDKey: string(scopeInstance.GetUID()),
scopeTemplateUIDKey: string(scopeTemplate.GetUID()),
clusterRoleBindingGenerateKey: "test"}
clusterRoleBindingGenerateKey: scopeTemplate.Spec.ClusterRoles[0].GenerateName}

roleBindingList := listRoleBinding(namespace.GetName(), 1, labels)

existingRB := &roleBindingList.Items[0]

verifyRoleBindings(existingRB, scopeInstance, scopeTemplate)
verifyScopeTemplateStatus(scopeTemplate, "Succeeded", "ScopeTemplateReconcileSuccess", metav1.ConditionTrue, "ScopeTemplate successfully reconciled")
})

When("a scopeInstance is updated to include another namespace", func() {
Expand Down Expand Up @@ -197,7 +206,7 @@ var _ = Describe("ScopeTemplate", func() {

labels := map[string]string{scopeInstanceUIDKey: string(scopeInstance.GetUID()),
scopeTemplateUIDKey: string(scopeTemplate.GetUID()),
clusterRoleBindingGenerateKey: "test"}
clusterRoleBindingGenerateKey: scopeTemplate.Spec.ClusterRoles[0].GenerateName}

roleBindingList := listRoleBinding(namespace2.GetName(), 1, labels)

Expand All @@ -209,6 +218,7 @@ var _ = Describe("ScopeTemplate", func() {

existingRB = &roleBindingList.Items[0]
verifyRoleBindings(existingRB, scopeInstance, scopeTemplate)
verifyScopeTemplateStatus(scopeTemplate, "Succeeded", "ScopeTemplateReconcileSuccess", metav1.ConditionTrue, "ScopeTemplate successfully reconciled")
})

When("a scopeInstance is updated to remove one of the namespace", func() {
Expand All @@ -227,7 +237,7 @@ var _ = Describe("ScopeTemplate", func() {

labels := map[string]string{scopeInstanceUIDKey: string(scopeInstance.GetUID()),
scopeTemplateUIDKey: string(scopeTemplate.GetUID()),
clusterRoleBindingGenerateKey: "test"}
clusterRoleBindingGenerateKey: scopeTemplate.Spec.ClusterRoles[0].GenerateName}

roleBindingList := listRoleBinding(namespace2.GetName(), 1, labels)

Expand All @@ -236,7 +246,7 @@ var _ = Describe("ScopeTemplate", func() {
verifyRoleBindings(existingRB, scopeInstance, scopeTemplate)

roleBindingList = listRoleBinding(namespace.GetName(), 0, labels)

verifyScopeTemplateStatus(scopeTemplate, "Succeeded", "ScopeTemplateReconcileSuccess", metav1.ConditionTrue, "ScopeTemplate successfully reconciled")
})

When("a scopeInstance is updated to remove all namespaces", func() {
Expand All @@ -259,7 +269,7 @@ var _ = Describe("ScopeTemplate", func() {
client.MatchingLabels{
scopeInstanceUIDKey: string(scopeInstance.GetUID()),
scopeTemplateUIDKey: string(scopeTemplate.GetUID()),
clusterRoleBindingGenerateKey: "test",
clusterRoleBindingGenerateKey: scopeTemplate.Spec.ClusterRoles[0].GenerateName,
})
if err != nil {
return err
Expand Down Expand Up @@ -290,19 +300,20 @@ var _ = Describe("ScopeTemplate", func() {
}))
Expect(existingCRB.RoleRef).To(Equal(rbacv1.RoleRef{
Kind: "ClusterRole",
Name: "test",
Name: scopeTemplate.Spec.ClusterRoles[0].GenerateName,
APIGroup: "rbac.authorization.k8s.io",
}))

labels := map[string]string{scopeInstanceUIDKey: string(scopeInstance.GetUID()),
scopeTemplateUIDKey: string(scopeTemplate.GetUID()),
clusterRoleBindingGenerateKey: "test"}
clusterRoleBindingGenerateKey: scopeTemplate.Spec.ClusterRoles[0].GenerateName}

roleBindingList := listRoleBinding(namespace.GetName(), 0, labels)
Expect(len(roleBindingList.Items)).To(Equal(0))

roleBindingList = listRoleBinding(namespace2.GetName(), 0, labels)
Expect(len(roleBindingList.Items)).To(Equal(0))
verifyScopeTemplateStatus(scopeTemplate, "Succeeded", "ScopeTemplateReconcileSuccess", metav1.ConditionTrue, "ScopeTemplate successfully reconciled")
})
})
})
Expand All @@ -329,11 +340,29 @@ func verifyRoleBindings(existingRB *rbacv1.RoleBinding, si *operatorsv1.ScopeIns
}))
Expect(existingRB.RoleRef).To(Equal(rbacv1.RoleRef{
Kind: "ClusterRole",
Name: "test",
Name: st.Spec.ClusterRoles[0].GenerateName,
APIGroup: "rbac.authorization.k8s.io",
}))
}

func verifyScopeTemplateStatus(st *operatorsv1.ScopeTemplate, conditionType string, reason string, status metav1.ConditionStatus, message string) {
tempSt := &operatorsv1.ScopeTemplate{}
Eventually(func() error {
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(st), tempSt)).NotTo(HaveOccurred())
if len(tempSt.Status.Conditions) == 0 {
return fmt.Errorf("status.conditions len not > 0")
}
return nil
}).Should(Succeed())

cond := meta.FindStatusCondition(tempSt.Status.Conditions, conditionType)
Expect(cond).ShouldNot(BeNil())
Expect(cond.ObservedGeneration).Should(Equal(tempSt.Generation))
Expect(cond.Reason).Should(Equal(reason))
Expect(cond.Status).Should(Equal(status))
Expect(cond.Message).Should(Equal(message))
}

func listRoleBinding(namespace string, numberOfExpectedRoleBindings int, labels map[string]string) *rbacv1.RoleBindingList {
roleBindingList := &rbacv1.RoleBindingList{}
Eventually(func() error {
Expand Down