Skip to content

Commit aea86d7

Browse files
Jayendra Parsaijgwest
authored andcommitted
fix: Cluster-scoped Rollouts should be restricted to user-defined namespace
Signed-off-by: Jayendra Parsai <[email protected]>
1 parent a96aa79 commit aea86d7

File tree

11 files changed

+188
-5
lines changed

11 files changed

+188
-5
lines changed

api/v1alpha1/argorollouts_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ const (
9999
RolloutManagerReasonErrorOccurred = "ErrorOccurred"
100100
RolloutManagerReasonMultipleClusterScopedRolloutManager = "MultipleClusterScopedRolloutManager"
101101
RolloutManagerReasonInvalidScoped = "InvalidRolloutManagerScope"
102+
RolloutManagerReasonInvalidNamespace = "InvalidRolloutManagerNamespace"
102103
)
103104

104105
type ResourceMetadata struct {

controllers/argorollouts_controller_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package rollouts
33
import (
44
"context"
55
"fmt"
6+
"os"
67

78
rolloutsmanagerv1alpha1 "github.com/argoproj-labs/argo-rollouts-manager/api/v1alpha1"
89
"github.com/argoproj-labs/argo-rollouts-manager/tests/e2e/fixture/k8s"
@@ -28,6 +29,14 @@ var _ = Describe("RolloutManagerReconciler tests", func() {
2829
BeforeEach(func() {
2930
ctx = context.Background()
3031
rm = makeTestRolloutManager()
32+
33+
By("Set Env variable.")
34+
os.Setenv(ClusterScopedArgoRolloutsNamespaces, rm.Namespace)
35+
})
36+
37+
AfterEach(func() {
38+
By("Unset Env variable.")
39+
os.Unsetenv(ClusterScopedArgoRolloutsNamespaces)
3140
})
3241

3342
When("NAMESPACE_SCOPED_ARGO_ROLLOUTS environment variable is set to False.", func() {
@@ -201,6 +210,9 @@ var _ = Describe("RolloutManagerReconciler tests", func() {
201210
rm2.Name = "test-rm"
202211
rm2.Namespace = "test-ns"
203212

213+
By("Update Env variable.")
214+
os.Setenv(ClusterScopedArgoRolloutsNamespaces, rm.Namespace+","+rm2.Namespace)
215+
204216
Expect(createNamespace(r, rm2.Namespace)).To(Succeed())
205217
Expect(r.Client.Create(ctx, rm2)).ToNot(HaveOccurred())
206218

controllers/default.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,7 @@ const (
3535
// NamespaceScopedArgoRolloutsController is an environment variable that can be used to configure scope of Argo Rollouts controller
3636
// Set true to allow only namespace-scoped Argo Rollouts controller deployment and false for cluster-scoped
3737
NamespaceScopedArgoRolloutsController = "NAMESPACE_SCOPED_ARGO_ROLLOUTS"
38+
39+
// ClusterScopedArgoRolloutsNamespaces is an environment variable that can be used to configure namespaces that are allowed to host cluster-scoped Argo Rollouts
40+
ClusterScopedArgoRolloutsNamespaces = "CLUSTER_SCOPED_ARGO_ROLLOUTS_NAMESPACES"
3841
)

controllers/reconcile.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ func (r *RolloutManagerReconciler) reconcileRolloutsManager(ctx context.Context,
2929
rr.condition = createCondition(err.Error(), rolloutsmanagerv1alpha1.RolloutManagerReasonInvalidScoped)
3030
return *rr, nil
3131
}
32+
33+
if invalidRolloutNamespace(err) {
34+
rr.condition = createCondition(err.Error(), rolloutsmanagerv1alpha1.RolloutManagerReasonInvalidNamespace)
35+
return *rr, nil
36+
}
37+
3238
log.Error(err, "failed to validate RolloutManager's scope.")
3339
return wrapCondition(createCondition(err.Error())), err
3440
}

controllers/resources_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package rollouts
33
import (
44
"context"
55
"fmt"
6+
"os"
67

78
"github.com/argoproj-labs/argo-rollouts-manager/api/v1alpha1"
89
monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
@@ -780,6 +781,12 @@ var _ = Describe("Resource creation and cleanup tests", func() {
780781
})
781782

782783
Context("Resource Cleanup test", func() {
784+
785+
AfterEach(func() {
786+
By("Unset Env variable.")
787+
os.Unsetenv(ClusterScopedArgoRolloutsNamespaces)
788+
})
789+
783790
a := makeTestRolloutManager()
784791
tt := []struct {
785792
name string
@@ -846,6 +853,9 @@ var _ = Describe("Resource creation and cleanup tests", func() {
846853
When(test.name, func() {
847854
It("Cleans up all resources created for RolloutManager", func() {
848855

856+
By("Set Env variable.")
857+
os.Setenv(ClusterScopedArgoRolloutsNamespaces, a.Namespace)
858+
849859
ctx := context.Background()
850860
req := reconcile.Request{
851861
NamespacedName: types.NamespacedName{
@@ -890,6 +900,14 @@ var _ = Describe("Resource creation and cleanup tests", func() {
890900
Namespace: a.Namespace,
891901
},
892902
}
903+
904+
By("Set Env variable.")
905+
os.Setenv(ClusterScopedArgoRolloutsNamespaces, a.Namespace)
906+
})
907+
908+
AfterEach(func() {
909+
By("Unset Env variable.")
910+
os.Unsetenv(ClusterScopedArgoRolloutsNamespaces)
893911
})
894912

895913
It("Verify whether RolloutManager creating ServiceMonitor", func() {

controllers/utils.go

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ import (
1717
)
1818

1919
const (
20-
UnsupportedRolloutManagerConfiguration = "when there exists a cluster-scoped RolloutManager on the cluster, there may not exist another: only a single cluster-scoped RolloutManager is supported"
21-
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"
22-
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"
20+
UnsupportedRolloutManagerConfiguration = "when there exists a cluster-scoped RolloutManager on the cluster, there may not exist another: only a single cluster-scoped RolloutManager is supported"
21+
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"
22+
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"
23+
UnsupportedRolloutManagerClusterScopedNamespace = "Namespace is not specified in CLUSTER_SCOPED_ARGO_ROLLOUTS_NAMESPACES environment variable of Subscription resource. If you wish to install a cluster-scoped Argo Rollouts instance outside the default namespace, ensure it is defined in CLUSTER_SCOPED_ARGO_ROLLOUTS_NAMESPACES"
2324
)
2425

2526
// pluginItem is a clone of PluginItem from "github.com/argoproj/argo-rollouts/utils/plugin/types"
@@ -235,11 +236,44 @@ func validateRolloutsScope(cr rolloutsmanagerv1alpha1.RolloutManager, namespaceS
235236
}, fmt.Errorf(UnsupportedRolloutManagerNamespaceScoped)
236237
}
237238

238-
// allow only cluster-scoped RolloutManager
239+
// if cluster-scoped RolloutManager being reconciled, is not specified in CLUSTER_SCOPED_ARGO_ROLLOUTS_NAMESPACES environment variable of Subscription resource,
240+
// then don't allow it.
241+
if !allowedClusterScopedNamespace(cr) {
242+
243+
phaseFailure := rolloutsmanagerv1alpha1.PhaseFailure
244+
245+
return &reconcileStatusResult{
246+
rolloutController: &phaseFailure,
247+
phase: &phaseFailure,
248+
}, errors.New(UnsupportedRolloutManagerClusterScopedNamespace)
249+
}
250+
251+
// allow cluster-scoped Rollouts for namespaces specified in CLUSTER_SCOPED_ARGO_ROLLOUTS_NAMESPACES environment variable of Subscription resource
239252
return nil, nil
240253
}
241254
}
242255

256+
// allowedClusterScopedNamespace will check that current namespace is allowed to host cluster-scoped Argo Rollouts.
257+
func allowedClusterScopedNamespace(cr rolloutsmanagerv1alpha1.RolloutManager) bool {
258+
clusterConfigNamespaces := splitList(os.Getenv(ClusterScopedArgoRolloutsNamespaces))
259+
if len(clusterConfigNamespaces) > 0 {
260+
for _, n := range clusterConfigNamespaces {
261+
if n == cr.Namespace {
262+
return true
263+
}
264+
}
265+
}
266+
return false
267+
}
268+
269+
func splitList(s string) []string {
270+
elems := strings.Split(s, ",")
271+
for i := range elems {
272+
elems[i] = strings.TrimSpace(elems[i])
273+
}
274+
return elems
275+
}
276+
243277
// checkForExistingRolloutManager will return error if more than one cluster-scoped RolloutManagers are created.
244278
// because only one cluster-scoped or all namespace-scoped RolloutManagers are supported.
245279
func checkForExistingRolloutManager(ctx context.Context, k8sClient client.Client, cr rolloutsmanagerv1alpha1.RolloutManager) (*reconcileStatusResult, error) {
@@ -290,6 +324,10 @@ func invalidRolloutScope(err error) bool {
290324
err.Error() == UnsupportedRolloutManagerNamespaceScoped
291325
}
292326

327+
func invalidRolloutNamespace(err error) bool {
328+
return err.Error() == UnsupportedRolloutManagerClusterScopedNamespace
329+
}
330+
293331
// updateStatusConditionOfRolloutManager calls Set Condition of RolloutManager status
294332
func updateStatusConditionOfRolloutManager(ctx context.Context, rr reconcileStatusResult, rm *rolloutsmanagerv1alpha1.RolloutManager, k8sClient client.Client, log logr.Logger) error {
295333

controllers/utils_test.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package rollouts
22

33
import (
44
"context"
5+
"os"
56

67
rolloutsmanagerv1alpha1 "github.com/argoproj-labs/argo-rollouts-manager/api/v1alpha1"
78
monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
@@ -360,9 +361,19 @@ var _ = Describe("validateRolloutsScope tests", func() {
360361

361362
When("NAMESPACE_SCOPED_ARGO_ROLLOUTS environment variable is set to False.", func() {
362363

364+
BeforeEach(func() {
365+
By("Set Env variable.")
366+
os.Setenv(ClusterScopedArgoRolloutsNamespaces, rolloutsManager.Namespace)
367+
})
368+
369+
AfterEach(func() {
370+
By("Unset Env variable.")
371+
os.Unsetenv(ClusterScopedArgoRolloutsNamespaces)
372+
})
373+
363374
namespaceScopedArgoRolloutsController := false
364375

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

367378
By("Create cluster-scoped RolloutManager.")
368379
Expect(k8sClient.Create(ctx, &rolloutsManager)).To(Succeed())
@@ -373,6 +384,22 @@ var _ = Describe("validateRolloutsScope tests", func() {
373384
Expect(rr).To(BeNil())
374385
})
375386

387+
It("should return error, if cluster-scoped RolloutManager is created in a namespace which is not specified in env variable.", func() {
388+
389+
By("Unset Env variable.")
390+
os.Unsetenv(ClusterScopedArgoRolloutsNamespaces)
391+
392+
By("Create cluster-scoped RolloutManager.")
393+
Expect(k8sClient.Create(ctx, &rolloutsManager)).To(Succeed())
394+
395+
By("Verify there is no error returned.")
396+
rr, err := validateRolloutsScope(rolloutsManager, namespaceScopedArgoRolloutsController)
397+
Expect(err).To(HaveOccurred())
398+
Expect(invalidRolloutNamespace(err)).To(BeTrue())
399+
Expect(*rr.phase).To(Equal(rolloutsmanagerv1alpha1.PhaseFailure))
400+
Expect(*rr.rolloutController).To(Equal(rolloutsmanagerv1alpha1.PhaseFailure))
401+
})
402+
376403
It("should return error, if namespace-scoped RolloutManager is created.", func() {
377404

378405
By("Create namespace-scoped RolloutManager.")

docs/usage/getting_started.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,49 @@ kubectl get all
2929

3030
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/).
3131

32+
33+
34+
35+
## Namespace Scoped Rollouts Instance
36+
37+
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`.
38+
39+
```yml
40+
apiVersion: argoproj.io/v1alpha1
41+
kind: RolloutManager
42+
metadata:
43+
name: argo-rollout
44+
spec:
45+
namespaceScoped: true
46+
```
47+
48+
49+
## Cluster Scoped Rollouts Instance
50+
51+
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 `false` 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.
52+
53+
```yml
54+
apiVersion: operators.coreos.com/v1alpha1
55+
kind: Subscription
56+
metadata:
57+
name: argo-operator
58+
spec:
59+
config:
60+
env:
61+
- name: NAMESPACE_SCOPED_ARGO_ROLLOUTS
62+
value: 'false'
63+
- name: CLUSTER_SCOPED_ARGO_ROLLOUTS_NAMESPACES
64+
value: <list of namespaces of cluster-scoped Argo CD instances>
65+
(...)
66+
```
67+
68+
Now set `spec.namespaceScoped` field to `false` to create a Rollouts instance.
69+
70+
```yml
71+
apiVersion: argoproj.io/v1alpha1
72+
kind: RolloutManager
73+
metadata:
74+
name: argo-rollout
75+
spec:
76+
namespaceScoped: false
77+
```

hack/run-upstream-argo-rollouts-e2e-tests.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ if [ -z "$SKIP_RUN_STEP" ]; then
4747
set +e
4848

4949
rm -f /tmp/e2e-operator-run.log || true
50+
51+
# Set namespaces used for cluster-scoped e2e tests
52+
export CLUSTER_SCOPED_ARGO_ROLLOUTS_NAMESPACES="argo-rollouts"
53+
5054
go run ./cmd/main.go 2>&1 | tee /tmp/e2e-operator-run.log &
5155

5256
set -e

hack/start-rollouts-manager-for-e2e-tests.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ set -ex
1818

1919
make install generate fmt vet
2020

21+
# Set namespaces used for cluster-scoped e2e tests
22+
export CLUSTER_SCOPED_ARGO_ROLLOUTS_NAMESPACES="argo-rollouts,test-rom-ns-1,rom-ns-1"
23+
2124
if [ "$RUN_IN_BACKGROUND" == "true" ]; then
2225
go run ./cmd/main.go 2>&1 | tee /tmp/e2e-operator-run.log &
2326
else

0 commit comments

Comments
 (0)