Skip to content

Commit

Permalink
fix: Cluster-scoped Rollouts should be restricted to user-defined nam…
Browse files Browse the repository at this point in the history
…espace

Signed-off-by: Jayendra Parsai <jparsai@jparsai-thinkpadp1gen4i.remote.csb>
  • Loading branch information
Jayendra Parsai committed Oct 1, 2024
1 parent 573c35a commit ceb5eed
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 5 deletions.
5 changes: 5 additions & 0 deletions controllers/argorollouts_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package rollouts
import (
"context"
"fmt"
"os"

rolloutsmanagerv1alpha1 "github.com/argoproj-labs/argo-rollouts-manager/api/v1alpha1"
"github.com/argoproj-labs/argo-rollouts-manager/tests/e2e/fixture/k8s"
Expand All @@ -28,6 +29,8 @@ var _ = Describe("RolloutManagerReconciler tests", func() {
BeforeEach(func() {
ctx = context.Background()
rm = makeTestRolloutManager()

os.Setenv(ClusterScopedArgoRolloutsNamespaces, rm.Namespace)
})

When("NAMESPACE_SCOPED_ARGO_ROLLOUTS environment variable is set to False.", func() {
Expand Down Expand Up @@ -201,6 +204,8 @@ var _ = Describe("RolloutManagerReconciler tests", func() {
rm2.Name = "test-rm"
rm2.Namespace = "test-ns"

os.Setenv(ClusterScopedArgoRolloutsNamespaces, rm.Namespace+","+rm2.Namespace)

Expect(createNamespace(r, rm2.Namespace)).To(Succeed())
Expect(r.Client.Create(ctx, rm2)).ToNot(HaveOccurred())

Expand Down
3 changes: 3 additions & 0 deletions controllers/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,7 @@ const (
// NamespaceScopedArgoRolloutsController is an environment variable that can be used to configure scope of Argo Rollouts controller
// Set true to allow only namespace-scoped Argo Rollouts controller deployment and false for cluster-scoped
NamespaceScopedArgoRolloutsController = "NAMESPACE_SCOPED_ARGO_ROLLOUTS"

// ClusterScopedArgoRolloutsNamespaces is an environment variable that can be used to configure namespaces that are allowed to host cluster-scoped Argo Rollouts
ClusterScopedArgoRolloutsNamespaces = "CLUSTER_SCOPED_ARGO_ROLLOUTS_NAMESPACES"
)
6 changes: 6 additions & 0 deletions controllers/resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package rollouts
import (
"context"
"fmt"
"os"

"github.com/argoproj-labs/argo-rollouts-manager/api/v1alpha1"
monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
Expand Down Expand Up @@ -875,6 +876,8 @@ var _ = Describe("Resource creation and cleanup tests", func() {
When(test.name, func() {
It("Cleans up all resources created for RolloutManager", func() {

os.Setenv(ClusterScopedArgoRolloutsNamespaces, a.Namespace)

ctx := context.Background()
req := reconcile.Request{
NamespacedName: types.NamespacedName{
Expand Down Expand Up @@ -919,6 +922,9 @@ var _ = Describe("Resource creation and cleanup tests", func() {
Namespace: a.Namespace,
},
}

By("Set Env variable.")
os.Setenv(ClusterScopedArgoRolloutsNamespaces, a.Namespace)
})

It("Verify whether RolloutManager creating ServiceMonitor", func() {
Expand Down
47 changes: 43 additions & 4 deletions controllers/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import (
)

const (
UnsupportedRolloutManagerConfiguration = "when there exists a cluster-scoped RolloutManager on the cluster, there may not exist another: only a single cluster-scoped RolloutManager is supported"
UnsupportedRolloutManagerClusterScoped = "when Subscription has environment variable NAMESPACE_SCOPED_ARGO_ROLLOUTS set to True, there may not exist any cluster-scoped RolloutManagers: in this case, only namespace-scoped RolloutManager resources are supported"
UnsupportedRolloutManagerNamespaceScoped = "when Subscription has environment variable NAMESPACE_SCOPED_ARGO_ROLLOUTS set to False, there may not exist any namespace-scoped RolloutManagers: only a single cluster-scoped RolloutManager is supported"
UnsupportedRolloutManagerConfiguration = "when there exists a cluster-scoped RolloutManager on the cluster, there may not exist another: only a single cluster-scoped RolloutManager is supported"
UnsupportedRolloutManagerClusterScoped = "when Subscription has environment variable NAMESPACE_SCOPED_ARGO_ROLLOUTS set to True, there may not exist any cluster-scoped RolloutManagers: in this case, only namespace-scoped RolloutManager resources are supported"
UnsupportedRolloutManagerNamespaceScoped = "when Subscription has environment variable NAMESPACE_SCOPED_ARGO_ROLLOUTS set to False, there may not exist any namespace-scoped RolloutManagers: only a single cluster-scoped RolloutManager is supported"
UnsupportedRolloutManagerClusterScopedNamespace = "Namespace is not specified in CLUSTER_SCOPED_ARGO_ROLLOUTS_NAMESPACES environment variable of Subscription resource"
)

// pluginItem is a clone of PluginItem from "github.com/argoproj/argo-rollouts/utils/plugin/types"
Expand Down Expand Up @@ -235,11 +236,49 @@ func validateRolloutsScope(cr rolloutsmanagerv1alpha1.RolloutManager, namespaceS
}, errors.New(UnsupportedRolloutManagerNamespaceScoped)
}

// allow only cluster-scoped RolloutManager
// if cluster-scoped RolloutManager being reconciled, is not specified in CLUSTER_SCOPED_ARGO_ROLLOUTS_NAMESPACES environment variable of Subscription resource,
// then don't allow it.
if !allowedClusterScopedNamespace(cr) {

phaseFailure := rolloutsmanagerv1alpha1.PhaseFailure

return &reconcileStatusResult{
rolloutController: &phaseFailure,
phase: &phaseFailure,
}, errors.New(UnsupportedRolloutManagerClusterScopedNamespace)
}

// allow cluster-scoped Rollouts for namespaces specified in CLUSTER_SCOPED_ARGO_ROLLOUTS_NAMESPACES environment variable of Subscription resource
return nil, nil
}
}

// allowedClusterScopedNamespace will check that current namespace is allowed to host cluster-scoped Argo Rollouts.
func allowedClusterScopedNamespace(cr rolloutsmanagerv1alpha1.RolloutManager) bool {
clusterConfigNamespaces := splitList(os.Getenv(ClusterScopedArgoRolloutsNamespaces))
if len(clusterConfigNamespaces) > 0 {
if clusterConfigNamespaces[0] == "*" {
return true
}

for _, n := range clusterConfigNamespaces {
if n == cr.Namespace {
return true
}
}
}

return false
}

func splitList(s string) []string {
elems := strings.Split(s, ",")
for i := range elems {
elems[i] = strings.TrimSpace(elems[i])
}
return elems
}

// checkForExistingRolloutManager will return error if more than one cluster-scoped RolloutManagers are created.
// because only one cluster-scoped or all namespace-scoped RolloutManagers are supported.
func checkForExistingRolloutManager(ctx context.Context, k8sClient client.Client, cr rolloutsmanagerv1alpha1.RolloutManager) (*reconcileStatusResult, error) {
Expand Down
22 changes: 21 additions & 1 deletion controllers/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package rollouts

import (
"context"
"os"

rolloutsmanagerv1alpha1 "github.com/argoproj-labs/argo-rollouts-manager/api/v1alpha1"
monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
Expand Down Expand Up @@ -362,7 +363,10 @@ var _ = Describe("validateRolloutsScope tests", func() {

namespaceScopedArgoRolloutsController := false

It("should not return error, if cluster-scoped RolloutManager is created.", func() {
It("should not return error, if cluster-scoped RolloutManager is created in a namespace specified in env variable.", func() {

By("Set Env variable.")
os.Setenv(ClusterScopedArgoRolloutsNamespaces, rolloutsManager.Namespace)

By("Create cluster-scoped RolloutManager.")
Expect(k8sClient.Create(ctx, &rolloutsManager)).To(Succeed())
Expand All @@ -373,6 +377,22 @@ var _ = Describe("validateRolloutsScope tests", func() {
Expect(rr).To(BeNil())
})

It("should return error, if cluster-scoped RolloutManager is created in a namespace which is not specified in env variable.", func() {

By("Unset Env variable.")
os.Unsetenv(ClusterScopedArgoRolloutsNamespaces)

By("Create cluster-scoped RolloutManager.")
Expect(k8sClient.Create(ctx, &rolloutsManager)).To(Succeed())

By("Verify there is no error returned.")
rr, err := validateRolloutsScope(rolloutsManager, namespaceScopedArgoRolloutsController)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal(UnsupportedRolloutManagerClusterScopedNamespace))
Expect(*rr.phase).To(Equal(rolloutsmanagerv1alpha1.PhaseFailure))
Expect(*rr.rolloutController).To(Equal(rolloutsmanagerv1alpha1.PhaseFailure))
})

It("should return error, if namespace-scoped RolloutManager is created.", func() {

By("Create namespace-scoped RolloutManager.")
Expand Down
53 changes: 53 additions & 0 deletions docs/usage/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,56 @@ kubectl get all

If you would like to understand the siginificance of each rollout controller resource created by the operator, please go through the official rollouts controller [docs](https://argo-rollouts.readthedocs.io/en/stable/).




## Namespace Scoped Rollouts Instance

A namespace-scoped Rollouts instance can manage Rollouts resources of same namespace it is deployed into. To deploy a namespace-scoped Rollouts instance set `spec.namespaceScoped` field to `true`.

```yml
apiVersion: argoproj.io/v1alpha1
kind: RolloutManager
metadata:
name: argo-rollout
labels:
example: basic
spec:
namespaceScoped: true
```
## Cluster Scoped Rollouts Instance
A cluster-scoped Rollouts instance can manage Rollouts resources from other namespaces as well. To install a cluster-scoped Rollouts instance first you need to add `NAMESPACE_SCOPED_ARGO_ROLLOUTS` and `CLUSTER_SCOPED_ARGO_ROLLOUTS_NAMESPACES` environment variables in subscription resource. If `NAMESPACE_SCOPED_ARGO_ROLLOUTS` is set to `true` then only you are allowed to create a cluster-scoped instance and then you need to provide list of namespaces that are allowed host a cluster-scoped Rollouts instance via `CLUSTER_SCOPED_ARGO_ROLLOUTS_NAMESPACES` environment variable.

```yml
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: argocd-operator
spec:
config:
env:
- name: NAMESPACE_SCOPED_ARGO_ROLLOUTS
value: 'false'
- name: CLUSTER_SCOPED_ARGO_ROLLOUTS_NAMESPACES
value: <list of namespaces of cluster-scoped Argo CD instances>
channel: alpha
name: argocd-operator
source: argocd-catalog
sourceNamespace: olm
```

Now set `spec.namespaceScoped` field to `false` to create a Rollouts instance.

```yml
apiVersion: argoproj.io/v1alpha1
kind: RolloutManager
metadata:
name: argo-rollout
labels:
example: basic
spec:
namespaceScoped: false
```

0 comments on commit ceb5eed

Please sign in to comment.