Skip to content

Instantly share code, notes, and snippets.

@displague
Created December 20, 2019 04:37
Show Gist options
  • Save displague/5b955fb471b6fcd9a2918212594d82df to your computer and use it in GitHub Desktop.
Save displague/5b955fb471b6fcd9a2918212594d82df to your computer and use it in GitHub Desktop.
diff --git a/design/one-pager-stacks-security-isolation.md b/design/one-pager-stacks-security-isolation.md
index 5700a46d..effe0164 100644
--- a/design/one-pager-stacks-security-isolation.md
+++ b/design/one-pager-stacks-security-isolation.md
@@ -342,15 +342,15 @@ The following built in roles will be installed and packaged with Crossplane, and
The verbs supported in these roles(admin,edit,view) will model behavior based on existing logic around
[built-in kubernetes roles](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#default-roles-and-role-bindings).
-|ClusterRole Name | Permissions |
-|:---------------------|:----------|
-| crossplane-admin | Admin: ClusterStacks, Crossplane CRDs, Namespaces, (Cluster)RoleBindings, (Cluster)Roles |
-| crossplane-env-admin | Admin: ClusterStacks, CustomResource types installed by Cluster Scoped Stacks |
-| crossplane-env-edit | Edit: ClusterStacks, CustomResource types installed by Cluster Scoped Stacks |
-| crossplane-env-view | View: CustomResource types installed by Cluster Scoped Stacks, Not permitted: secrets |
+|ClusterRole Name | Kubernetes Counterpart | Permissions |
+|:---------------------|:----------|:----------|
+| crossplane-admin | cluster-admin | Admin: ClusterStacks, Crossplane CRDs, Namespaces, (Cluster)RoleBindings, (Cluster)Roles |
+| crossplane-env-admin | admin | Admin: ClusterStacks, CustomResource types installed by Cluster Scoped Stacks |
+| crossplane-env-edit | edit | Edit: ClusterStacks, CustomResource types installed by Cluster Scoped Stacks |
+| crossplane-env-view | view | View: CustomResource types installed by Cluster Scoped Stacks, Not permitted: secrets |
We will provide a convenience group - `crossplane:masters` - that binds to `crossplane-admin` with a
- ClusterRoleBinding. This mirrors Kubernetes functionality for cluster-admin and system:master. At install time you will
+ ClusterRoleBinding. This mirrors Kubernetes functionality for `cluster-admin` and `system:masters`. At install time you will
then be able to impersonate a group that is bound to crossplane-admin.
Example running a command as crossplane-admin
@@ -369,10 +369,11 @@ Match labels on these roles for auto-aggregation:
Each namespace provides a unit of isolation with namespaced stacks, and thus a unique set of resources, based on the
stacks that have been installed into that namespace. If someone were allowed to install stacks, it can't be assumed
that they are cluster-admin, so the stack-manager will install and manage a set of roles that will aggregate the roles
- for the installed stacks into namespace specific roles for self-service. These roles should be bound to subjects
- to grant access to a particular namespace for end-user integration. For any namespace that is annotated with
- `rbac.crossplane.io/managed-roles: true` the stack-manager will ensure that ClusterRoles in the following format are
- created for the annotated namespace.
+ for the installed stacks into namespace specific roles for self-service. These `ClusterRoles` should be bound to subjects
+ using a `RoleBinding` (not `ClusterRoleBinding`) to grant access to a particular namespace for end-user integration.
+
+This design calls for the stack-manager to act on any namespace that is annotated with `rbac.crossplane.io/managed-roles: true`,
+ensuring that ClusterRoles in the following format are created for the annotated namespace.
* `crossplane:ns:{ns-name}:admin`
* `crossplane:ns:{ns-name}:edit`
@@ -386,6 +387,13 @@ Match Roles:
* `rbac.crossplane.io/aggregate-to-namespace-{role}: "true"`
* `namespace.crossplane.io/{namespace-name}: true // to match specific namespace.`
+|ClusterRole Name | Kubernetes Counterpart | Permissions |
+|:---------------------|:----------|:----------|
+| crossplane:ns:{ns-name}:admin | admin | Admin: StackInstalls, Stacks, ConfigMaps, Secrets, CustomResource types installed by Namespace Scoped Stacks |
+| crossplane:ns:{ns-name}:edit | edit | Edit: StackInstalls, ConfigMaps, Secrets, CustomResource types installed by Namespace Scoped Stacks |
+| crossplane:ns:{ns-name}:view | view | View: StackInstalls, CustomResource types installed by Namespace Scoped Stacks, Not permitted: secrets |
+
+
#### ClusterRoles for a given (Cluster)Stack Install
For a given stack version the stack-manager will create a unique set of roles in the format
@@ -394,14 +402,16 @@ For a given stack version the stack-manager will create a unique set of roles in
stack. The other (admin,edit,view) will be labeled for aggregation to their appropriate namespaced or environment
scoped crossplane managed roles.
-Consider the following example for a wordpress stack of version 1.1 from wordpressinc, you would have the following set.
- * `stack:wordpressinc:wordpress:1.1:system` // Used by wordpress operator - stack resources + dependent resources grants
+Consider the following example for a wordpress stack of version 1.1 from wordpressinc (installed in a "wordpressinc" namespace),
+you would have the following set.
+
+* `stack:wordpressinc:wordpress:1.1:system` // Used by wordpress operator - stack resources + dependent resources grants
* `stack:wordpressinc:wordpress:1.1:admin`
* `stack:wordpressinc:wordpress:1.1:edit`
* `stack:wordpressinc:wordpress:1.1:view`
If these were a from a cluster scoped stack, they would be aggregated to the built in environment roles, and if they
- were from a namespaced stack install they would be aggregated to the corresponding [ClusterRoles for a given app
+ were from a namespaced `StackInstall` they would be aggregated to the corresponding [ClusterRoles for a given app
namespace](#clusterroles-for-a-given-app-namespace).
Labels for these roles will be for aggregation purposes:
@@ -421,6 +431,9 @@ We've defined the roles that get created that will aggregate the permissions of
bound to RBAC subjects, like a group or User, but these roles themselves need to be seeded with initial permissions
so that a user who has a namespace or environment level role could for example install the first stack.
+These roles share a common function to the Kubernetes `system:aggregate-to-view`, `system:aggregate-to-edit`,
+and `system:aggregate-to-admin` clusterroles.
+
Defaults that stack-manager installs that would aggregate to the crossplane-env-{role} `ClusterRole` objects,
for example, permission on `ClusterStackInstall` objects.
@@ -495,7 +508,7 @@ metadata:
rules:
- apiGroups: ["providers.aws.crossplane.io"]
resources: ["Provider"]
- verbs: ["get", "list", "watch", "edit", "delete"]
+ verbs: ["get", "list", "watch", "create", "update", "patch", "deletecollection", "delete"]
...
```
@@ -513,7 +526,7 @@ metadata:
rules:
- apiGroups: ["wordpress.crossplane.io"]
resources: ["WordpressInstance"]
- verbs: ["get", "list", "watch", "edit", "delete"]
+ verbs: ["get", "list", "watch"]
...
```
diff --git a/docs/install-crossplane.md b/docs/install-crossplane.md
index ae6d471f..b69188c9 100644
--- a/docs/install-crossplane.md
+++ b/docs/install-crossplane.md
@@ -240,6 +240,7 @@ The following tables lists the configurable parameters of the Crossplane chart a
| `clusterStacks.azure.version` | Azure stack version to deploy | `<latest released version>`
| `clusterStacks.rook.deploy` | Deploy Rook stack | `false`
| `clusterStacks.rook.version` | Rook stack version to deploy | `<latest released version>`
+| `personas.deploy` | Install roles and bindings for Crossplane user personas | `true`
### Command Line
diff --git a/pkg/controller/stacks/install/installjob.go b/pkg/controller/stacks/install/installjob.go
index 7af3e7a2..c82a54b5 100644
--- a/pkg/controller/stacks/install/installjob.go
+++ b/pkg/controller/stacks/install/installjob.go
@@ -46,7 +46,6 @@ var (
jobBackoff = int32(0)
registryDirName = ".registry"
packageContentsVolumeName = "package-contents"
- labelNamespaceFmt = "namespace.crossplane.io/%s"
)
// JobCompleter is an interface for handling job completion
@@ -251,8 +250,13 @@ func (jc *stackInstallJobCompleter) createJobOutputObject(ctx context.Context, o
// instead.
labels := stacks.ParentLabels(i)
+ // CRDs are labeled with the namespaces of the stacks they are managed by.
+ // This will allow for a single Namespaced stack to be installed in multiple
+ // namespaces, or different stacks (possibly only differing by versions) to
+ // provide the same CRDs without the risk that a single StackInstall removal
+ // will delete a CRD until there are no remaining namespace labels.
if isCRDObject(obj) {
- labelNamespace := fmt.Sprintf(labelNamespaceFmt, i.GetNamespace())
+ labelNamespace := fmt.Sprintf(stacks.LabelNamespaceFmt, i.GetNamespace())
labels[labelNamespace] = "true"
}
diff --git a/pkg/controller/stacks/install/installjob_test.go b/pkg/controller/stacks/install/installjob_test.go
index e2cb4e16..96992de6 100644
--- a/pkg/controller/stacks/install/installjob_test.go
+++ b/pkg/controller/stacks/install/installjob_test.go
@@ -562,13 +562,13 @@ func TestCreate(t *testing.T) {
func TestCreateJobOutputObject(t *testing.T) {
wantLabels := map[string]string{
- stacks.LabelParentGroup: "stacks.crossplane.io",
- stacks.LabelParentVersion: "v1alpha1",
- stacks.LabelParentKind: "StackInstall",
- stacks.LabelParentNamespace: namespace,
- stacks.LabelParentName: resourceName,
- stacks.LabelParentUID: uidString,
- fmt.Sprintf(labelNamespaceFmt, namespace): "true",
+ stacks.LabelParentGroup: "stacks.crossplane.io",
+ stacks.LabelParentVersion: "v1alpha1",
+ stacks.LabelParentKind: "StackInstall",
+ stacks.LabelParentNamespace: namespace,
+ stacks.LabelParentName: resourceName,
+ stacks.LabelParentUID: uidString,
+ fmt.Sprintf(stacks.LabelNamespaceFmt, namespace): "true",
}
type want struct {
diff --git a/pkg/controller/stacks/install/stackinstall.go b/pkg/controller/stacks/install/stackinstall.go
index 8c11237c..6d266618 100644
--- a/pkg/controller/stacks/install/stackinstall.go
+++ b/pkg/controller/stacks/install/stackinstall.go
@@ -255,27 +255,31 @@ func (h *stackInstallHandler) update(ctx context.Context) (reconcile.Result, err
// This function ensures that all the resources (e.g., CRDs) that this StackInstall owns
// are also cleaned up.
func (h *stackInstallHandler) delete(ctx context.Context) (reconcile.Result, error) {
+ // Delete all Stacks created by this StackInstall or ClusterStackInstall
labels := stacks.ParentLabels(h.ext)
if err := h.kube.DeleteAllOf(ctx, &v1alpha1.Stack{}, client.InNamespace(h.ext.GetNamespace()), client.MatchingLabels(labels)); runtimeresource.IgnoreNotFound(err) != nil {
return fail(ctx, h.kube, h.ext, err)
}
+ // Waiting for all Stacks to clear their finalizers and delete before
+ // deleting the CRDs that they depend on
stackList := &v1alpha1.StackList{}
if err := h.kube.List(ctx, stackList, client.MatchingLabels(labels)); err != nil {
return fail(ctx, h.kube, h.ext, err)
}
- // Waiting for all Stacks to be clear their finalizers and delete before
- // deleting the CRDs that they depend on
if len(stackList.Items) != 0 {
err := errors.New("Stack resources have not been deleted")
return fail(ctx, h.kube, h.ext, err)
}
+ // Once the Stacks are gone, we can remove all of the CRDs associated
+ // with the StackInstall
if err := h.kube.DeleteAllOf(ctx, &apiextensionsv1beta1.CustomResourceDefinition{}, client.MatchingLabels(labels)); runtimeresource.IgnoreNotFound(err) != nil {
return fail(ctx, h.kube, h.ext, err)
}
+ // And finally clear the StackInstall's own finalizer
meta.RemoveFinalizer(h.ext, installFinalizer)
if err := h.kube.Update(ctx, h.ext); err != nil {
return fail(ctx, h.kube, h.ext, err)
diff --git a/pkg/controller/stacks/stack/stack.go b/pkg/controller/stacks/stack/stack.go
index b8866723..a1945bd7 100644
--- a/pkg/controller/stacks/stack/stack.go
+++ b/pkg/controller/stacks/stack/stack.go
@@ -44,12 +44,8 @@ import (
)
const (
- controllerName = "stack.stacks.crossplane.io"
- clusterRoleNameFmt = "stack:%s:%s:%s:%s"
- labelScope = "crossplane.io/scope"
- labelNamespaceFmt = "namespace.crossplane.io/%s"
- labelAggregateFmt = "rbac.crossplane.io/aggregate-to-%s-%s"
- stacksFinalizer = "finalizer.stacks.crossplane.io"
+ controllerName = "stack.stacks.crossplane.io"
+ stacksFinalizer = "finalizer.stacks.crossplane.io"
reconcileTimeout = 1 * time.Minute
requeueAfterOnSuccess = 10 * time.Second
@@ -61,8 +57,8 @@ var (
requeueOnSuccess = reconcile.Result{RequeueAfter: requeueAfterOnSuccess}
roleVerbs = map[string][]string{
- "admin": {"get", "list", "watch", "edit", "create", "delete"},
- "edit": {"get", "list", "watch", "edit"},
+ "admin": {"get", "list", "watch", "create", "delete", "deletecollection", "patch", "update"},
+ "edit": {"get", "list", "watch", "create", "delete", "deletecollection", "patch", "update"},
"view": {"get", "list", "watch"},
}
)
@@ -191,13 +187,34 @@ func (h *stackHandler) update(ctx context.Context) (reconcile.Result, error) {
// createPersonaClusterRoles creates admin, edit, and view clusterroles that are
// namespace+stack+version specific
func (h *stackHandler) createPersonaClusterRoles(ctx context.Context, labels map[string]string) error {
- // TODO(displague) refine 'editor' persona roles
for persona := range roleVerbs {
+ name := stacks.PersonaRoleName(h.ext, persona)
+
+ // Use a copy so AddLabels doesn't mutate labels
labelsCopy := map[string]string{}
for k, v := range labels {
labelsCopy[k] = v
}
+ // Create labels appropriate for the scope of the ClusterRole
+ var crossplaneScope string
+
+ if h.isNamespaced() {
+ crossplaneScope = stacks.NamespaceScoped
+
+ labelNamespace := fmt.Sprintf(stacks.LabelNamespaceFmt, h.ext.GetNamespace())
+ labelsCopy[labelNamespace] = "true"
+ } else {
+ crossplaneScope = stacks.EnvironmentScoped
+ }
+
+ // Aggregation labels grant Stack CRD responsibilities
+ // to namespace or environment personas like crossplane-env-admin
+ // or crossplane:ns:default:view
+ aggregationLabel := fmt.Sprintf(stacks.LabelAggregateFmt, crossplaneScope, persona)
+ labelsCopy[aggregationLabel] = "true"
+
+ // Each ClusterRole needs persona specific rules for each CRD
rules := []rbacv1.PolicyRule{}
for _, crd := range h.ext.Spec.CRDs {
rules = append(rules, rbacv1.PolicyRule{
@@ -206,18 +223,8 @@ func (h *stackHandler) createPersonaClusterRoles(ctx context.Context, labels map
Verbs: roleVerbs[persona],
})
}
- name := fmtPersonaRole(h.ext, persona)
-
- var crossplaneScope string
-
- if h.isNamespaced() {
- crossplaneScope = "namespace"
- } else {
- crossplaneScope = "environment"
- }
-
- aggregationLabel := fmt.Sprintf(labelAggregateFmt, crossplaneScope, persona)
+ // Assemble and create the ClusterRole
cr := &rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{
Name: name,
@@ -226,18 +233,6 @@ func (h *stackHandler) createPersonaClusterRoles(ctx context.Context, labels map
Rules: rules,
}
- meta.AddLabels(cr, map[string]string{
- aggregationLabel: "true",
- })
-
- meta.AddLabels(cr, stacks.ParentLabels(h.ext))
-
- if h.isNamespaced() {
- meta.AddLabels(cr, map[string]string{
- fmt.Sprintf(labelNamespaceFmt, h.ext.GetNamespace()): "true",
- })
- }
-
if err := h.kube.Create(ctx, cr); err != nil && !kerrors.IsAlreadyExists(err) {
return errors.Wrap(err, "failed to create persona cluster roles")
}
@@ -245,40 +240,40 @@ func (h *stackHandler) createPersonaClusterRoles(ctx context.Context, labels map
return nil
}
+// generateNamespaceClusterRoles generates the crossplane:ns:{name}:{persona}
+// roles for a given stack's namespace.
func generateNamespaceClusterRoles(stack *v1alpha1.Stack) (roles []*rbacv1.ClusterRole) {
personas := []string{"admin", "edit", "view"}
- namespaced := (stack.Spec.PermissionScope == string(apiextensions.NamespaceScoped))
- if !namespaced {
- return
- }
-
nsName := stack.GetNamespace()
for _, persona := range personas {
- name := fmt.Sprintf("crossplane:ns:%s:%s", nsName, persona)
+ name := fmt.Sprintf(stacks.NamespaceClusterRoleNameFmt, nsName, persona)
labels := map[string]string{
- fmt.Sprintf(labelNamespaceFmt, nsName): "true",
- labelScope: "namespace",
+ fmt.Sprintf(stacks.LabelNamespaceFmt, nsName): "true",
+ stacks.LabelScope: stacks.NamespaceScoped,
}
if persona == "admin" {
- labels[fmt.Sprintf(labelAggregateFmt, "crossplane", persona)] = "true"
+ labels[fmt.Sprintf(stacks.LabelAggregateFmt, "crossplane", persona)] = "true"
}
+ // By specifying MatchLabels, ClusterRole Aggregation will pass
+ // along the rules from other ClusterRoles with the desired labels.
+ // This is why we don't define any Rules here.
role := &rbacv1.ClusterRole{
AggregationRule: &rbacv1.AggregationRule{
ClusterRoleSelectors: []metav1.LabelSelector{
{
MatchLabels: map[string]string{
- fmt.Sprintf(labelAggregateFmt, "namespace", persona): "true",
- fmt.Sprintf(labelNamespaceFmt, nsName): "true",
+ fmt.Sprintf(stacks.LabelAggregateFmt, stacks.NamespaceScoped, persona): "true",
+ fmt.Sprintf(stacks.LabelNamespaceFmt, nsName): "true",
},
},
{
MatchLabels: map[string]string{
- fmt.Sprintf(labelAggregateFmt, "namespace-default", persona): "true",
+ fmt.Sprintf(stacks.LabelAggregateFmt, "namespace-default", persona): "true",
},
},
},
@@ -297,16 +292,28 @@ func generateNamespaceClusterRoles(stack *v1alpha1.Stack) (roles []*rbacv1.Clust
return roles
}
+// createNamespaceClusterRoles creates the crossplane:ns:{name}:{persona}
+// roles for a given stack's namespace
func (h *stackHandler) createNamespaceClusterRoles(ctx context.Context) error {
+ if !h.isNamespaced() {
+ return nil
+ }
+
+ // Get the Namepsace because we need the UID for OwnerReference
ns := &corev1.Namespace{}
nsName := h.ext.GetNamespace()
if err := h.kube.Get(ctx, types.NamespacedName{Name: nsName}, ns); err != nil {
- return errors.Wrapf(err, "failed to get namespace %q for stackinstall %q", nsName, h.ext.GetName())
+ return errors.Wrapf(err, "failed to get namespace %q for stack %q", nsName, h.ext.GetName())
}
+
+ // generate namespace + stack specific clusterroles for all personas
roles := generateNamespaceClusterRoles(h.ext)
for _, role := range roles {
+ // When the namespace is deleted, we no longer need these clusterroles.
+ // Set the ClusterRole owner to the Namespace so they are cleaned up
+ // automatically
role.SetOwnerReferences([]metav1.OwnerReference{
{
APIVersion: "v1",
@@ -316,15 +323,18 @@ func (h *stackHandler) createNamespaceClusterRoles(ctx context.Context) error {
},
})
+ // Creating the clusterroles. Since these Rules in these clusterroles
+ // are populated through aggregation from the stacks installed in the
+ // namespaces, we won't need to update them.
if err := h.kube.Create(ctx, role); err != nil && !kerrors.IsAlreadyExists(err) {
- return errors.Wrapf(err, "failed to create clusterrole %s for stackinstall %s", role.GetName(), h.ext.GetName())
+ return errors.Wrapf(err, "failed to create clusterrole %s for stack %s", role.GetName(), h.ext.GetName())
}
}
return nil
}
func (h *stackHandler) createDeploymentClusterRole(ctx context.Context, labels map[string]string) (string, error) {
- name := fmtPersonaRole(h.ext, "system")
+ name := stacks.PersonaRoleName(h.ext, "system")
cr := &rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{
Name: name,
@@ -538,9 +548,3 @@ func fail(ctx context.Context, kube client.StatusClient, i *v1alpha1.Stack, err
i.Status.SetConditions(runtimev1alpha1.ReconcileError(err))
return resultRequeue, kube.Status().Update(ctx, i)
}
-
-// fmtPersonaRole is a helper to ensure the persona role formatting parameters
-// are provided consistently
-func fmtPersonaRole(stack *v1alpha1.Stack, persona string) string {
- return fmt.Sprintf(clusterRoleNameFmt, stack.GetNamespace(), stack.GetName(), stack.Spec.Version, persona)
-}
diff --git a/pkg/stacks/clusterrole.go b/pkg/stacks/clusterrole.go
new file mode 100644
index 00000000..64634da2
--- /dev/null
+++ b/pkg/stacks/clusterrole.go
@@ -0,0 +1,53 @@
+/*
+Copyright 2019 The Crossplane Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package stacks
+
+import (
+ "fmt"
+
+ "github.com/crossplaneio/crossplane/apis/stacks/v1alpha1"
+)
+
+// Labels used to track ownership across namespaces and scopes.
+const (
+ // rbac.crossplane.io/aggregate-to-{scope}-{persona}
+ // {scope} is namespace or environment and may include "-default"
+ // persona is one of admin, edit, or view
+ LabelAggregateFmt = "rbac.crossplane.io/aggregate-to-%s-%s"
+
+ // namespace.crossplane.io/{namespace}
+ LabelNamespaceFmt = "namespace.crossplane.io/%s"
+
+ LabelScope = "crossplane.io/scope"
+
+ // crossplane:ns:{namespace}:{persona}
+ NamespaceClusterRoleNameFmt = "crossplane:ns:%s:%s"
+)
+
+// Crossplane ClusterRole Scopes
+const (
+ NamespaceScoped = "namespace"
+ EnvironmentScoped = "environment"
+)
+
+// PersonaRoleName is a helper to ensure the persona role formatting parameters
+// are provided consistently
+func PersonaRoleName(stack *v1alpha1.Stack, persona string) string {
+ const clusterRoleNameFmt = "stack:%s:%s:%s:%s"
+
+ return fmt.Sprintf(clusterRoleNameFmt, stack.GetNamespace(), stack.GetName(), stack.Spec.Version, persona)
+}
diff --git a/pkg/stacks/relationship.go b/pkg/stacks/relationship.go
index 3fcefce2..51da86f6 100644
--- a/pkg/stacks/relationship.go
+++ b/pkg/stacks/relationship.go
@@ -37,6 +37,7 @@ type KindlyIdentifier interface {
GetName() string
GetNamespace() string
GetUID() types.UID
+
GroupVersionKind() schema.GroupVersionKind
}
diff --git a/pkg/stacks/unpack.go b/pkg/stacks/unpack.go
index 14f804ad..aada1c02 100644
--- a/pkg/stacks/unpack.go
+++ b/pkg/stacks/unpack.go
@@ -73,7 +73,6 @@ const (
// Stack CRD Labels
labelKubernetesManagedBy = "app.kubernetes.io/managed-by"
- labelScope = "crossplane.io/scope"
)
var (
@@ -291,9 +290,9 @@ func (sp *StackPackage) AddCRD(path string, crd *apiextensions.CustomResourceDef
crd.ObjectMeta.Labels[labelKubernetesManagedBy] = "stack-manager"
if sp.IsNamespaced() {
- crd.ObjectMeta.Labels[labelScope] = "namespace"
+ crd.ObjectMeta.Labels[LabelScope] = NamespaceScoped
} else {
- crd.ObjectMeta.Labels[labelScope] = "environment"
+ crd.ObjectMeta.Labels[LabelScope] = EnvironmentScoped
}
crdGVK := schema.GroupVersionKind{
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment