From 67dd4c0f9479d077f4013d5f0d06ac5a575655fe Mon Sep 17 00:00:00 2001
From: Dennis Nguyen <1750375+dnguy078@users.noreply.github.com>
Date: Mon, 21 Aug 2023 07:34:25 -0700
Subject: [PATCH] feat: add support for Argo Rollouts resource (#166)
---
API.md | 4 +-
Makefile | 1 +
...rds.wizardofoz.co_execaccesstemplates.yaml | 4 +
...crds.wizardofoz.co_podaccesstemplates.yaml | 140 ++++++++++-------
config/rbac/role.yaml | 8 +
examples/rollout.yaml | 31 ++++
go.mod | 5 +-
go.sum | 10 +-
.../cross_version_object_reference.go | 5 +-
.../api/v1alpha1/pod_spec_mutation_config.go | 2 -
.../create_access_resources_test.go | 146 +++++++++++++++++-
.../builders/podaccessbuilder/suite_test.go | 33 +++-
internal/builders/podaccessbuilder/types.go | 1 +
.../utils/get_pod_template_from_controller.go | 17 +-
internal/builders/utils/get_rollout.go | 29 ++++
.../builders/utils/get_selector_labels.go | 11 ++
internal/cmd/manager/main.go | 2 +
.../templatecontroller/controller.go | 1 +
18 files changed, 372 insertions(+), 78 deletions(-)
create mode 100644 examples/rollout.yaml
create mode 100644 internal/builders/utils/get_rollout.go
diff --git a/API.md b/API.md
index 90e60f52..be9b8ef4 100644
--- a/API.md
+++ b/API.md
@@ -1096,9 +1096,7 @@ that should be applied. The primary use case is in the PodAccessTemplate, where
controller (Deployment, DaemonSet, StatefulSet) can be used as the reference for the PodSpec
that is launched for the user. However, the operator may want to make modifications to the
PodSpec at launch time (eg, change the entrypoint command or arguments).
-TODO: Add podAnnotations
-TODO: Add podLabels
-TODO: Add affinity
+TODO: Add affinity
diff --git a/Makefile b/Makefile
index d5bb774a..496c928a 100644
--- a/Makefile
+++ b/Makefile
@@ -101,6 +101,7 @@ vet: ## Run go vet against code.
.PHONY: test
test: manifests generate envtest ## Run tests.
+ go mod download
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test -v $(shell go list ./... | grep -v 'e2e') -coverprofile cover.out -covermode=atomic -race
##@ Build
diff --git a/config/crd/bases/crds.wizardofoz.co_execaccesstemplates.yaml b/config/crd/bases/crds.wizardofoz.co_execaccesstemplates.yaml
index 95b3b02c..2f0d6bb1 100644
--- a/config/crd/bases/crds.wizardofoz.co_execaccesstemplates.yaml
+++ b/config/crd/bases/crds.wizardofoz.co_execaccesstemplates.yaml
@@ -86,6 +86,9 @@ spec:
description: "Defines the \"APIVersion\" of the resource being
referred to. Eg, \"apps/v1\". \n TODO: Figure out how to regex
validate that it has a \"/\" in it"
+ enum:
+ - apps/v1
+ - argoproj.io/v1alpha1
type: string
kind:
description: Defines the "Kind" of resource being referred to.
@@ -93,6 +96,7 @@ spec:
- Deployment
- DaemonSet
- StatefulSet
+ - Rollout
type: string
name:
description: Defines the "metadata.Name" of the target resource.
diff --git a/config/crd/bases/crds.wizardofoz.co_podaccesstemplates.yaml b/config/crd/bases/crds.wizardofoz.co_podaccesstemplates.yaml
index 64d2c44a..5f11bd34 100644
--- a/config/crd/bases/crds.wizardofoz.co_podaccesstemplates.yaml
+++ b/config/crd/bases/crds.wizardofoz.co_podaccesstemplates.yaml
@@ -343,6 +343,9 @@ spec:
description: "Defines the \"APIVersion\" of the resource being
referred to. Eg, \"apps/v1\". \n TODO: Figure out how to regex
validate that it has a \"/\" in it"
+ enum:
+ - apps/v1
+ - argoproj.io/v1alpha1
type: string
kind:
description: Defines the "Kind" of resource being referred to.
@@ -350,6 +353,7 @@ spec:
- Deployment
- DaemonSet
- StatefulSet
+ - Rollout
type: string
name:
description: Defines the "metadata.Name" of the target resource.
@@ -2118,6 +2122,27 @@ spec:
value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
type: object
type: object
+ restartPolicy:
+ description: 'RestartPolicy defines the restart behavior
+ of individual containers in a pod. This field may only
+ be set for init containers, and the only allowed value
+ is "Always". For non-init containers or when this field
+ is not specified, the restart behavior is defined by the
+ Pod''s restart policy and the container type. Setting
+ the RestartPolicy as "Always" for the init container will
+ have the following effect: this init container will be
+ continually restarted on exit until all regular containers
+ have terminated. Once all regular containers have completed,
+ all init containers with restartPolicy "Always" will be
+ shut down. This lifecycle differs from normal init containers
+ and is often referred to as a "sidecar" container. Although
+ this init container still starts in the init container
+ sequence, it does not wait for the container to complete
+ before proceeding to the next init container. Instead,
+ the next init container starts immediately after this
+ init container is started, or after any startupProbe has
+ successfully completed.'
+ type: string
securityContext:
description: 'SecurityContext defines the security options
the container should be run with. If set, the fields of
@@ -2242,7 +2267,8 @@ spec:
The profile must be preconfigured on the node
to work. Must be a descending path, relative to
the kubelet's configured seccomp profile location.
- Must only be set if type is "Localhost".
+ Must be set if type is "Localhost". Must NOT be
+ set for any other type.
type: string
type:
description: "type indicates which kind of seccomp
@@ -2275,15 +2301,11 @@ spec:
type: string
hostProcess:
description: HostProcess determines if a container
- should be run as a 'Host Process' container. This
- field is alpha-level and will only be honored
- by components that enable the WindowsHostProcessContainers
- feature flag. Setting this field without the feature
- flag will result in errors when validating the
- Pod. All of a Pod's containers must have the same
- effective HostProcess value (it is not allowed
- to have a mix of HostProcess containers and non-HostProcess
- containers). In addition, if HostProcess is true
+ should be run as a 'Host Process' container. All
+ of a Pod's containers must have the same effective
+ HostProcess value (it is not allowed to have a
+ mix of HostProcess containers and non-HostProcess
+ containers). In addition, if HostProcess is true
then HostNetwork must also be set to true.
type: boolean
runAsUserName:
@@ -3468,6 +3490,12 @@ spec:
value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
type: object
type: object
+ restartPolicy:
+ description: Restart policy for the container to manage
+ the restart behavior of each container within a pod. This
+ may only be set for init containers. You cannot set this
+ field on ephemeral containers.
+ type: string
securityContext:
description: 'Optional: SecurityContext defines the security
options the ephemeral container should be run with. If
@@ -3592,7 +3620,8 @@ spec:
The profile must be preconfigured on the node
to work. Must be a descending path, relative to
the kubelet's configured seccomp profile location.
- Must only be set if type is "Localhost".
+ Must be set if type is "Localhost". Must NOT be
+ set for any other type.
type: string
type:
description: "type indicates which kind of seccomp
@@ -3625,15 +3654,11 @@ spec:
type: string
hostProcess:
description: HostProcess determines if a container
- should be run as a 'Host Process' container. This
- field is alpha-level and will only be honored
- by components that enable the WindowsHostProcessContainers
- feature flag. Setting this field without the feature
- flag will result in errors when validating the
- Pod. All of a Pod's containers must have the same
- effective HostProcess value (it is not allowed
- to have a mix of HostProcess containers and non-HostProcess
- containers). In addition, if HostProcess is true
+ should be run as a 'Host Process' container. All
+ of a Pod's containers must have the same effective
+ HostProcess value (it is not allowed to have a
+ mix of HostProcess containers and non-HostProcess
+ containers). In addition, if HostProcess is true
then HostNetwork must also be set to true.
type: boolean
runAsUserName:
@@ -4851,6 +4876,27 @@ spec:
value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
type: object
type: object
+ restartPolicy:
+ description: 'RestartPolicy defines the restart behavior
+ of individual containers in a pod. This field may only
+ be set for init containers, and the only allowed value
+ is "Always". For non-init containers or when this field
+ is not specified, the restart behavior is defined by the
+ Pod''s restart policy and the container type. Setting
+ the RestartPolicy as "Always" for the init container will
+ have the following effect: this init container will be
+ continually restarted on exit until all regular containers
+ have terminated. Once all regular containers have completed,
+ all init containers with restartPolicy "Always" will be
+ shut down. This lifecycle differs from normal init containers
+ and is often referred to as a "sidecar" container. Although
+ this init container still starts in the init container
+ sequence, it does not wait for the container to complete
+ before proceeding to the next init container. Instead,
+ the next init container starts immediately after this
+ init container is started, or after any startupProbe has
+ successfully completed.'
+ type: string
securityContext:
description: 'SecurityContext defines the security options
the container should be run with. If set, the fields of
@@ -4975,7 +5021,8 @@ spec:
The profile must be preconfigured on the node
to work. Must be a descending path, relative to
the kubelet's configured seccomp profile location.
- Must only be set if type is "Localhost".
+ Must be set if type is "Localhost". Must NOT be
+ set for any other type.
type: string
type:
description: "type indicates which kind of seccomp
@@ -5008,15 +5055,11 @@ spec:
type: string
hostProcess:
description: HostProcess determines if a container
- should be run as a 'Host Process' container. This
- field is alpha-level and will only be honored
- by components that enable the WindowsHostProcessContainers
- feature flag. Setting this field without the feature
- flag will result in errors when validating the
- Pod. All of a Pod's containers must have the same
- effective HostProcess value (it is not allowed
- to have a mix of HostProcess containers and non-HostProcess
- containers). In addition, if HostProcess is true
+ should be run as a 'Host Process' container. All
+ of a Pod's containers must have the same effective
+ HostProcess value (it is not allowed to have a
+ mix of HostProcess containers and non-HostProcess
+ containers). In addition, if HostProcess is true
then HostNetwork must also be set to true.
type: boolean
runAsUserName:
@@ -5431,18 +5474,13 @@ spec:
as this pod. \n The template will be used to create
a new ResourceClaim, which will be bound to this pod.
When this pod is deleted, the ResourceClaim will also
- be deleted. The name of the ResourceClaim will be
- -, where
- is the PodResourceClaim.Name. Pod validation will
- reject the pod if the concatenated name is not valid
- for a ResourceClaim (e.g. too long). \n An existing
- ResourceClaim with that name that is not owned by
- the pod will not be used for the pod to avoid using
- an unrelated resource by mistake. Scheduling and pod
- startup are then blocked until the unrelated ResourceClaim
- is removed. \n This field is immutable and no changes
- will be made to the corresponding ResourceClaim by
- the control plane after creating the ResourceClaim."
+ be deleted. The pod name and resource name, along
+ with a generated component, will be used to form a
+ unique name for the ResourceClaim, which will be recorded
+ in pod.status.resourceClaimStatuses. \n This field
+ is immutable and no changes will be made to the corresponding
+ ResourceClaim by the control plane after creating
+ the ResourceClaim."
type: string
type: object
required:
@@ -5585,7 +5623,8 @@ spec:
in a file on the node should be used. The profile must
be preconfigured on the node to work. Must be a descending
path, relative to the kubelet's configured seccomp profile
- location. Must only be set if type is "Localhost".
+ location. Must be set if type is "Localhost". Must NOT
+ be set for any other type.
type: string
type:
description: "type indicates which kind of seccomp profile
@@ -5650,15 +5689,12 @@ spec:
type: string
hostProcess:
description: HostProcess determines if a container should
- be run as a 'Host Process' container. This field is
- alpha-level and will only be honored by components that
- enable the WindowsHostProcessContainers feature flag.
- Setting this field without the feature flag will result
- in errors when validating the Pod. All of a Pod's containers
- must have the same effective HostProcess value (it is
- not allowed to have a mix of HostProcess containers
- and non-HostProcess containers). In addition, if HostProcess
- is true then HostNetwork must also be set to true.
+ be run as a 'Host Process' container. All of a Pod's
+ containers must have the same effective HostProcess
+ value (it is not allowed to have a mix of HostProcess
+ containers and non-HostProcess containers). In addition,
+ if HostProcess is true then HostNetwork must also be
+ set to true.
type: boolean
runAsUserName:
description: The UserName in Windows to run the entrypoint
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index f68c656c..7dafe13f 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -34,6 +34,14 @@ rules:
- get
- list
- watch
+- apiGroups:
+ - argoproj.io
+ resources:
+ - rollouts
+ verbs:
+ - get
+ - list
+ - watch
- apiGroups:
- crds.wizardofoz.co
resources:
diff --git a/examples/rollout.yaml b/examples/rollout.yaml
new file mode 100644
index 00000000..2931cc87
--- /dev/null
+++ b/examples/rollout.yaml
@@ -0,0 +1,31 @@
+apiVersion: argoproj.io/v1alpha1
+kind: Rollout
+metadata:
+ name: rollouts-demo
+spec:
+ replicas: 5
+ strategy:
+ canary:
+ steps:
+ - setWeight: 20
+ - pause: {duration: 1}
+ revisionHistoryLimit: 2
+ selector:
+ matchLabels:
+ app: rollouts-demo
+ template:
+ metadata:
+ labels:
+ app: rollouts-demo
+ spec:
+ containers:
+ - name: rollouts-demo
+ image: nginx:latest
+ ports:
+ - name: http
+ containerPort: 80
+ protocol: TCP
+ resources:
+ requests:
+ memory: 32Mi
+ cpu: 5m
diff --git a/go.mod b/go.mod
index 80258590..e582663e 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module github.com/diranged/oz
go 1.20
require (
+ github.com/argoproj/argo-rollouts v1.5.1
github.com/fatih/color v1.15.0
github.com/go-logr/logr v1.2.4
github.com/ivanpirog/coloredcobra v1.0.1
@@ -34,14 +35,14 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
- github.com/google/btree v1.0.1 // indirect
+ github.com/google/btree v1.1.2 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0 // indirect
- github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
+ github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index 830ff211..8d4b87d5 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,7 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/argoproj/argo-rollouts v1.5.1 h1:P1C6oIWn6fwtPvB3u04NQlUGIv8cq/aJvUkbwciuWYo=
+github.com/argoproj/argo-rollouts v1.5.1/go.mod h1:OaOf+oZawsss6fy+9WEDy4IaSbwuRteBj1X2QiVfqdA=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -64,8 +66,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
-github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
+github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
+github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -86,8 +88,8 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
-github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
+github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
diff --git a/internal/api/v1alpha1/cross_version_object_reference.go b/internal/api/v1alpha1/cross_version_object_reference.go
index 69991984..21fab7e7 100644
--- a/internal/api/v1alpha1/cross_version_object_reference.go
+++ b/internal/api/v1alpha1/cross_version_object_reference.go
@@ -18,11 +18,12 @@ type CrossVersionObjectReference struct {
// TODO: Figure out how to regex validate that it has a "/" in it
//
// +kubebuilder:validation:Required
+ // +kubebuilder:validation:Enum=apps/v1;argoproj.io/v1alpha1
APIVersion string `json:"apiVersion"`
// Defines the "Kind" of resource being referred to.
// +kubebuilder:validation:Required
- // +kubebuilder:validation:Enum=Deployment;DaemonSet;StatefulSet
+ // +kubebuilder:validation:Enum=Deployment;DaemonSet;StatefulSet;Rollout
Kind ControllerKind `json:"kind"`
// Defines the "metadata.Name" of the target resource.
@@ -68,7 +69,7 @@ func (r *CrossVersionObjectReference) GetGroupVersionKind() schema.GroupVersionK
}
}
-// GetObject returns a generic unstrucutred resource that points to the desired API object. Because
+// GetObject returns a generic unstructured resource that points to the desired API object. Because
// this is unstructured (for now), you can really only use this to get metadata back from the API
// about the resource.
//
diff --git a/internal/api/v1alpha1/pod_spec_mutation_config.go b/internal/api/v1alpha1/pod_spec_mutation_config.go
index 6a2f503a..f9fd8599 100644
--- a/internal/api/v1alpha1/pod_spec_mutation_config.go
+++ b/internal/api/v1alpha1/pod_spec_mutation_config.go
@@ -24,8 +24,6 @@ const (
// that is launched for the user. However, the operator may want to make modifications to the
// PodSpec at launch time (eg, change the entrypoint command or arguments).
//
-// TODO: Add podAnnotations
-// TODO: Add podLabels
// TODO: Add affinity
type PodTemplateSpecMutationConfig struct {
// DefaultContainerName allows the operator to define which container is considered the default
diff --git a/internal/builders/podaccessbuilder/create_access_resources_test.go b/internal/builders/podaccessbuilder/create_access_resources_test.go
index 6f53bcdc..1088b4cf 100644
--- a/internal/builders/podaccessbuilder/create_access_resources_test.go
+++ b/internal/builders/podaccessbuilder/create_access_resources_test.go
@@ -7,6 +7,7 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
+ rolloutsv1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
@@ -22,12 +23,14 @@ import (
var _ = Describe("RequestReconciler", Ordered, func() {
Context("CreateAccessResources()", func() {
var (
- ctx = context.Background()
- ns *corev1.Namespace
- deployment *appsv1.Deployment
- request *v1alpha1.PodAccessRequest
- template *v1alpha1.PodAccessTemplate
- builder = PodAccessBuilder{}
+ ctx = context.Background()
+ ns *corev1.Namespace
+ deployment *appsv1.Deployment
+ request *v1alpha1.PodAccessRequest
+ rolloutRequest *v1alpha1.PodAccessRequest
+ template *v1alpha1.PodAccessTemplate
+ rolloutTemplate *v1alpha1.PodAccessTemplate
+ builder = PodAccessBuilder{}
)
BeforeAll(func() {
@@ -72,6 +75,39 @@ var _ = Describe("RequestReconciler", Ordered, func() {
err = k8sClient.Create(ctx, deployment)
Expect(err).To(Not(HaveOccurred()))
+ By("Creating a Rollout to reference for the test")
+ rollout := &rolloutsv1alpha1.Rollout{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "rollout-test",
+ Namespace: ns.Name,
+ },
+ Spec: rolloutsv1alpha1.RolloutSpec{
+ Selector: &metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "testLabel": "testValue",
+ },
+ },
+ Template: corev1.PodTemplateSpec{
+ ObjectMeta: metav1.ObjectMeta{
+ Labels: map[string]string{
+ "testLabel": "testValue",
+ },
+ },
+ Spec: corev1.PodSpec{
+ Containers: []corev1.Container{
+ {
+ Name: "test",
+ Image: "nginx:latest",
+ },
+ },
+ },
+ },
+ },
+ }
+
+ err = k8sClient.Create(ctx, rollout)
+ Expect(err).To(Not(HaveOccurred()))
+
By("Should have an PodAccessTemplate to test against")
cpuReq, _ := resource.ParseQuantity("1")
template = &v1alpha1.PodAccessTemplate{
@@ -108,6 +144,40 @@ var _ = Describe("RequestReconciler", Ordered, func() {
err = k8sClient.Create(ctx, template)
Expect(err).ToNot(HaveOccurred())
+ rolloutTemplate = &v1alpha1.PodAccessTemplate{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: utils.RandomString(8),
+ Namespace: ns.GetName(),
+ },
+ Spec: v1alpha1.PodAccessTemplateSpec{
+ AccessConfig: v1alpha1.AccessConfig{
+ AllowedGroups: []string{"testGroupA"},
+ DefaultDuration: "1h",
+ MaxDuration: "2h",
+ },
+ ControllerTargetRef: &v1alpha1.CrossVersionObjectReference{
+ APIVersion: "argoproj.io/v1alpha1",
+ Kind: "Rollout",
+ Name: "rollout-test",
+ },
+ ControllerTargetMutationConfig: &v1alpha1.PodTemplateSpecMutationConfig{
+ DefaultContainerName: "test",
+ Command: &[]string{"/bin/sleep"},
+ Args: &[]string{"100"},
+ Env: []corev1.EnvVar{
+ {Name: "FOO", Value: "BAR"},
+ },
+ Resources: corev1.ResourceRequirements{
+ Requests: map[corev1.ResourceName]resource.Quantity{
+ "cpu": cpuReq,
+ },
+ },
+ },
+ },
+ }
+ err = k8sClient.Create(ctx, rolloutTemplate)
+ Expect(err).ToNot(HaveOccurred())
+
By("Should have an PodAccessRequest built to test against")
request = &v1alpha1.PodAccessRequest{
ObjectMeta: metav1.ObjectMeta{
@@ -120,6 +190,19 @@ var _ = Describe("RequestReconciler", Ordered, func() {
}
err = k8sClient.Create(ctx, request)
Expect(err).ToNot(HaveOccurred())
+
+ // verify podaccess request with Rollout
+ rolloutRequest = &v1alpha1.PodAccessRequest{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "createaccessresource-rollout-test",
+ Namespace: ns.GetName(),
+ },
+ Spec: v1alpha1.PodAccessRequestSpec{
+ TemplateName: rolloutTemplate.GetName(),
+ },
+ }
+ err = k8sClient.Create(ctx, rolloutRequest)
+ Expect(err).ToNot(HaveOccurred())
})
AfterAll(func() {
@@ -178,5 +261,56 @@ var _ = Describe("RequestReconciler", Ordered, func() {
Expect(foundRoleBinding.RoleRef.Name).To(Equal(foundRole.GetName()))
Expect(foundRoleBinding.Subjects[0].Name).To(Equal("testGroupA"))
})
+
+ It("CreateAccessResources() should succeed with Rollout", func() {
+ rolloutRequest.Status.PodName = ""
+
+ // Execute
+ ret, err := builder.CreateAccessResources(ctx, k8sClient, rolloutRequest, rolloutTemplate)
+
+ // VERIFY: No error returned
+ Expect(err).ToNot(HaveOccurred())
+
+ // VERIFY: Proper status string returned
+ Expect(ret).To(MatchRegexp(fmt.Sprintf(
+ "Success. Pod %s-.*, Role %s-.*, RoleBinding %s.* created",
+ rolloutRequest.GetName(),
+ rolloutRequest.GetName(),
+ rolloutRequest.GetName(),
+ )))
+
+ // VERIFY: Pod Created as expected
+ foundPod := &corev1.Pod{}
+ err = k8sClient.Get(ctx, types.NamespacedName{
+ Name: bldutil.GenerateResourceName(rolloutRequest),
+ Namespace: ns.GetName(),
+ }, foundPod)
+ Expect(err).ToNot(HaveOccurred())
+ Expect(foundPod.GetOwnerReferences()).ToNot(BeNil())
+ Expect(foundPod.Spec.Containers[0].Command[0]).To(Equal("/bin/sleep"))
+ Expect(foundPod.Spec.Containers[0].Args[0]).To(Equal("100"))
+
+ // VERIFY: Role Created as expected
+ foundRole := &rbacv1.Role{}
+ err = k8sClient.Get(ctx, types.NamespacedName{
+ Name: bldutil.GenerateResourceName(rolloutRequest),
+ Namespace: ns.GetName(),
+ }, foundRole)
+ Expect(err).ToNot(HaveOccurred())
+ Expect(foundRole.GetOwnerReferences()).ToNot(BeNil())
+ Expect(foundRole.Rules[0].ResourceNames[0]).To(Equal(foundPod.GetName()))
+ Expect(foundRole.Rules[1].ResourceNames[0]).To(Equal(foundPod.GetName()))
+
+ // VERIFY: RoleBinding Created as expected
+ foundRoleBinding := &rbacv1.RoleBinding{}
+ err = k8sClient.Get(ctx, types.NamespacedName{
+ Name: bldutil.GenerateResourceName(rolloutRequest),
+ Namespace: ns.GetName(),
+ }, foundRoleBinding)
+ Expect(err).ToNot(HaveOccurred())
+ Expect(foundRoleBinding.GetOwnerReferences()).ToNot(BeNil())
+ Expect(foundRoleBinding.RoleRef.Name).To(Equal(foundRole.GetName()))
+ Expect(foundRoleBinding.Subjects[0].Name).To(Equal("testGroupA"))
+ })
})
})
diff --git a/internal/builders/podaccessbuilder/suite_test.go b/internal/builders/podaccessbuilder/suite_test.go
index be23a77d..356156e5 100644
--- a/internal/builders/podaccessbuilder/suite_test.go
+++ b/internal/builders/podaccessbuilder/suite_test.go
@@ -17,13 +17,16 @@ limitations under the License.
package podaccessbuilder
import (
+ "fmt"
+ "os/exec"
"path/filepath"
+ "strings"
"testing"
+ rolloutsv1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"go.uber.org/zap/zapcore"
-
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -59,12 +62,21 @@ var _ = BeforeSuite(func() {
logf.SetLogger(logger)
By("bootstrapping test environment")
+
+ var err error
+
+ // grab go mod directory with Argo rollout CRD to be installed into test environment cluster
+ argoCRDPath, err := extractCRDPath("github.com/argoproj/argo-rollouts", "manifests/crds")
+ Expect(err).NotTo(HaveOccurred())
+
testEnv = &envtest.Environment{
- CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")},
+ CRDDirectoryPaths: []string{
+ filepath.Join("..", "..", "..", "config", "crd", "bases"),
+ argoCRDPath,
+ },
ErrorIfCRDPathMissing: true,
}
- var err error
// cfg is defined in this file globally.
cfg, err = testEnv.Start()
Expect(err).NotTo(HaveOccurred())
@@ -73,8 +85,10 @@ var _ = BeforeSuite(func() {
err = crdsv1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
- //+kubebuilder:scaffold:scheme
+ err = rolloutsv1alpha1.AddToScheme(scheme.Scheme)
+ Expect(err).NotTo(HaveOccurred())
+ //+kubebuilder:scaffold:scheme
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient).NotTo(BeNil())
@@ -85,3 +99,14 @@ var _ = AfterSuite(func() {
err := testEnv.Stop()
Expect(err).NotTo(HaveOccurred())
})
+
+// extractCRDPath returns an absolute path to CRD path given a package dependency
+func extractCRDPath(dep string, crdRelativePath string) (string, error) {
+ p, err := exec.Command("go", "list", "-m", "-f", "{{.Dir}}", dep).Output()
+ if err != nil {
+ return "", err
+ }
+ crdPath := fmt.Sprintf("%s/%s", string(p), crdRelativePath)
+ crdPath = strings.ReplaceAll(crdPath, "\n", "")
+ return crdPath, nil
+}
diff --git a/internal/builders/podaccessbuilder/types.go b/internal/builders/podaccessbuilder/types.go
index bd8e3931..85b44388 100644
--- a/internal/builders/podaccessbuilder/types.go
+++ b/internal/builders/podaccessbuilder/types.go
@@ -14,6 +14,7 @@ import (
//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=get;list;watch;create;update;patch;delete;bind;escalate
//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=argoproj.io,resources=rollouts,verbs=get;list;watch
// defaultReadyWaitTime is the default time in which we wait for resources to
// become Ready in the AccessResourcesAreReady() method.
diff --git a/internal/builders/utils/get_pod_template_from_controller.go b/internal/builders/utils/get_pod_template_from_controller.go
index 9ea05999..db288033 100644
--- a/internal/builders/utils/get_pod_template_from_controller.go
+++ b/internal/builders/utils/get_pod_template_from_controller.go
@@ -2,7 +2,7 @@ package utils
import (
"context"
- "errors"
+ "fmt"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -12,7 +12,9 @@ import (
)
// GetPodTemplateFromController will return a PodTemplate resource from an
-// understood controller type (Deployment, DaemonSet or StatefulSet).
+// understood controller type (Deployment, DaemonSet, Rollout, or StatefulSet).
+//
+// revive:disable:cyclomatic
func GetPodTemplateFromController(
ctx context.Context,
client client.Client,
@@ -36,6 +38,15 @@ func GetPodTemplateFromController(
}
return *controller.Spec.Template.DeepCopy(), nil
+ case "Rollout":
+ controller, err := getRollout(ctx, client, targetController)
+ if err != nil {
+ log.Error(err, "Failed to find target Rollout")
+ return corev1.PodTemplateSpec{}, err
+ }
+
+ return *controller.Spec.Template.DeepCopy(), nil
+
case "DaemonSet":
controller, err := getDaemonSet(ctx, client, targetController)
if err != nil {
@@ -53,6 +64,6 @@ func GetPodTemplateFromController(
return *controller.Spec.Template.DeepCopy(), nil
default:
- return corev1.PodTemplateSpec{}, errors.New("invalid input")
+ return corev1.PodTemplateSpec{}, fmt.Errorf("invalid input %s", kind)
}
}
diff --git a/internal/builders/utils/get_rollout.go b/internal/builders/utils/get_rollout.go
new file mode 100644
index 00000000..09807448
--- /dev/null
+++ b/internal/builders/utils/get_rollout.go
@@ -0,0 +1,29 @@
+package utils
+
+import (
+ "context"
+
+ rolloutsv1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
+ "k8s.io/apimachinery/pkg/types"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// getRollout returns a Rollout given the supplied generic client.Object resource
+//
+// Returns:
+//
+// v1alpha1.Rollout: A populated Rollout object
+// error: Any error that may have occurred
+func getRollout(
+ ctx context.Context,
+ client client.Client,
+ obj client.Object,
+) (*rolloutsv1alpha1.Rollout, error) {
+ found := &rolloutsv1alpha1.Rollout{}
+ err := client.Get(ctx, types.NamespacedName{
+ Name: obj.GetName(),
+ Namespace: obj.GetNamespace(),
+ }, found)
+
+ return found, err
+}
diff --git a/internal/builders/utils/get_selector_labels.go b/internal/builders/utils/get_selector_labels.go
index 03600183..226ebdd9 100644
--- a/internal/builders/utils/get_selector_labels.go
+++ b/internal/builders/utils/get_selector_labels.go
@@ -18,6 +18,7 @@ import (
// - Deployment
// - DaemonSet
// - StatefulSet
+// - Rollout
//
// https://medium.com/coding-kubernetes/using-k8s-label-selectors-in-go-the-right-way-733cde7e8630
//
@@ -25,6 +26,8 @@ import (
//
// - labels.Selector: A populated labels.Selector which can be used when searching for Pods
// - error
+//
+// revive:disable:cyclomatic
func GetSelectorLabels(
ctx context.Context,
client client.Client,
@@ -48,6 +51,14 @@ func GetSelectorLabels(
}
return metav1.LabelSelectorAsSelector(controller.Spec.Selector)
+ case "Rollout":
+ controller, err := getRollout(ctx, client, targetController)
+ if err != nil {
+ log.Error(err, "Failed to find target Rollout")
+ return nil, err
+ }
+ return metav1.LabelSelectorAsSelector(controller.Spec.Selector)
+
case "DaemonSet":
controller, err := getDaemonSet(ctx, client, targetController)
if err != nil {
diff --git a/internal/cmd/manager/main.go b/internal/cmd/manager/main.go
index b93c63da..7f5230c7 100644
--- a/internal/cmd/manager/main.go
+++ b/internal/cmd/manager/main.go
@@ -23,6 +23,7 @@ import (
"os"
"time"
+ rolloutsv1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
@@ -56,6 +57,7 @@ var (
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
+ utilruntime.Must(rolloutsv1alpha1.AddToScheme(scheme))
utilruntime.Must(crdsv1alpha1.AddToScheme(scheme))
//+kubebuilder:scaffold:scheme
diff --git a/internal/controllers/templatecontroller/controller.go b/internal/controllers/templatecontroller/controller.go
index 115dc0f5..f0658abf 100644
--- a/internal/controllers/templatecontroller/controller.go
+++ b/internal/controllers/templatecontroller/controller.go
@@ -24,6 +24,7 @@ import (
//+kubebuilder:rbac:groups=crds.wizardofoz.co,resources=podaccesstemplates/finalizers,verbs=update
//+kubebuilder:rbac:groups=apps,resources=deployments;daemonsets;statefulsets,verbs=get;list;watch
+//+kubebuilder:rbac:groups=argoproj.io,resources=rollouts,verbs=get;list;watch
// Reconcile is a high level entrypoint triggered by Watches on particular
// Custom Resources within the cluster. This wrapper handles a few common