From 43161766a88765edf7764f4d60cc087c6514377b Mon Sep 17 00:00:00 2001 From: Simon Beck Date: Wed, 22 Jan 2020 14:27:23 +0100 Subject: [PATCH 1/2] Finalising cluster logic and add unit tests This commit finalises the first draft for the cluster controller logic and adds unit tests to all controllers. Signed-off-by: Simon Beck --- .gitignore | 2 + .vscode/launch.json | 17 --- cmd/manager/main.go | 4 + deploy/crds/syn.tools_clusters_crd.yaml | 45 ++++--- deploy/crds/syn.tools_gitrepos_crd.yaml | 23 +++- deploy/crds/syn.tools_tenants_crd.yaml | 22 +++- .../crds/syn.tools_v1alpha1_cluster_cr.yaml | 17 ++- .../crds/syn.tools_v1alpha1_gitrepo_cr.yaml | 6 +- deploy/crds/syn.tools_v1alpha1_tenant_cr.yaml | 15 ++- go.mod | 5 +- go.sum | 14 ++- pkg/apis/constants.go | 5 + pkg/apis/syn/v1alpha1/cluster_types.go | 17 +-- pkg/apis/syn/v1alpha1/gitrepo_types.go | 12 +- pkg/apis/syn/v1alpha1/tenant_types.go | 9 +- .../syn/v1alpha1/zz_generated.deepcopy.go | 37 ++---- pkg/controller/cluster/cluster_controller.go | 6 +- pkg/controller/cluster/cluster_reconcile.go | 61 +++++++-- .../cluster/cluster_reconcile_test.go | 118 ++++++++++++++++++ pkg/controller/gitrepo/gitrepo_reconcile.go | 3 - pkg/controller/tenant/tenant_controller.go | 6 +- pkg/controller/tenant/tenant_reconcile.go | 25 ++-- .../tenant/tenant_reconcile_test.go | 96 ++++++++++++++ pkg/helpers/crd.go | 17 ++- 24 files changed, 433 insertions(+), 149 deletions(-) delete mode 100644 .vscode/launch.json create mode 100644 pkg/apis/constants.go create mode 100644 pkg/controller/cluster/cluster_reconcile_test.go create mode 100644 pkg/controller/tenant/tenant_reconcile_test.go diff --git a/.gitignore b/.gitignore index 5f61db5b..66b03d44 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ build/_output/* +.vscode/ +.idea/ diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 51cc97ac..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Launch", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "${workspaceFolder}/cmd/manager/main.go", - "env": {}, - "args": [] - } - ] -} diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 9e25edd7..afd48a83 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "runtime" + "time" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) _ "k8s.io/client-go/plugin/pkg/client/auth" @@ -90,10 +91,13 @@ func main() { os.Exit(1) } + duration := 30 * time.Second + // Create a new Cmd to provide shared dependencies and start components mgr, err := manager.New(cfg, manager.Options{ Namespace: namespace, MetricsBindAddress: fmt.Sprintf("%s:%d", metricsHost, metricsPort), + SyncPeriod: &duration, }) if err != nil { log.Error(err, "") diff --git a/deploy/crds/syn.tools_clusters_crd.yaml b/deploy/crds/syn.tools_clusters_crd.yaml index 20aa8c98..8f13dfa2 100644 --- a/deploy/crds/syn.tools_clusters_crd.yaml +++ b/deploy/crds/syn.tools_clusters_crd.yaml @@ -3,6 +3,13 @@ kind: CustomResourceDefinition metadata: name: clusters.syn.tools spec: + additionalPrinterColumns: + - JSONPath: .spec.displayName + name: Display Name + type: string + - JSONPath: .spec.tenantRef.name + name: Tenant + type: string group: syn.tools names: kind: Cluster @@ -31,27 +38,12 @@ spec: spec: description: ClusterSpec defines the desired state of Cluster properties: - apiEndpointSecretRef: - description: APIEndpointSecretRef references the secret containing connection - information to Kubernetes API endpoint of the registered Kubernetes - cluster. - properties: - name: - description: Name is unique within a namespace to reference a secret - resource. - type: string - namespace: - description: Namespace defines the space within which the secret - name must be unique. - type: string - type: object displayName: description: DisplayName of cluster which could be different from metadata.name. Allows cluster renaming should it be needed. type: string facts: additionalProperties: - description: FactValue is the value for the facts map type: string description: Facts are key/value pairs for statically configured facts type: object @@ -75,9 +67,7 @@ spec: type: string type: object deployKeys: - description: DeployKeys optional list of SSH deploy keys. If - not set, not deploy keys will be configured - items: + additionalProperties: description: DeployKey defines an SSH key to be used for git operations. properties: @@ -88,7 +78,9 @@ spec: writeAccess: type: boolean type: object - type: array + description: DeployKeys optional list of SSH deploy keys. If + not set, not deploy keys will be configured + type: object path: description: Path to Git repository type: string @@ -100,10 +92,14 @@ spec: to properties: name: - type: string - namespace: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' type: string type: object + required: + - apiSecretRef + - path + - repoName type: object type: object gitRepoURL: @@ -115,13 +111,16 @@ spec: to. properties: name: - type: string - namespace: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' type: string type: object tokenLifeTime: description: TokenLifetime set the token lifetime type: string + required: + - displayName + - tenantRef type: object status: description: ClusterStatus defines the observed state of Cluster diff --git a/deploy/crds/syn.tools_gitrepos_crd.yaml b/deploy/crds/syn.tools_gitrepos_crd.yaml index 3c224ed2..4e3e1def 100644 --- a/deploy/crds/syn.tools_gitrepos_crd.yaml +++ b/deploy/crds/syn.tools_gitrepos_crd.yaml @@ -3,6 +3,13 @@ kind: CustomResourceDefinition metadata: name: gitrepos.syn.tools spec: + additionalPrinterColumns: + - JSONPath: .spec.repoName + name: Repo Name + type: string + - JSONPath: .status.phase + name: Phase + type: string group: syn.tools names: kind: GitRepo @@ -45,9 +52,7 @@ spec: type: string type: object deployKeys: - description: DeployKeys optional list of SSH deploy keys. If not set, - not deploy keys will be configured - items: + additionalProperties: description: DeployKey defines an SSH key to be used for git operations. properties: key: @@ -57,7 +62,9 @@ spec: writeAccess: type: boolean type: object - type: array + description: DeployKeys optional list of SSH deploy keys. If not set, + not deploy keys will be configured + type: object path: description: Path to Git repository type: string @@ -68,10 +75,14 @@ spec: description: TenantRef references the tenant this repo belongs to properties: name: - type: string - namespace: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' type: string type: object + required: + - apiSecretRef + - path + - repoName type: object status: description: GitRepoStatus defines the observed state of GitRepo diff --git a/deploy/crds/syn.tools_tenants_crd.yaml b/deploy/crds/syn.tools_tenants_crd.yaml index edf663cf..bc209e11 100644 --- a/deploy/crds/syn.tools_tenants_crd.yaml +++ b/deploy/crds/syn.tools_tenants_crd.yaml @@ -3,6 +3,10 @@ kind: CustomResourceDefinition metadata: name: tenants.syn.tools spec: + additionalPrinterColumns: + - JSONPath: .spec.displayName + name: Display Name + type: string group: syn.tools names: kind: Tenant @@ -55,9 +59,7 @@ spec: type: string type: object deployKeys: - description: DeployKeys optional list of SSH deploy keys. If - not set, not deploy keys will be configured - items: + additionalProperties: description: DeployKey defines an SSH key to be used for git operations. properties: @@ -68,7 +70,9 @@ spec: writeAccess: type: boolean type: object - type: array + description: DeployKeys optional list of SSH deploy keys. If + not set, not deploy keys will be configured + type: object path: description: Path to Git repository type: string @@ -80,16 +84,22 @@ spec: to properties: name: - type: string - namespace: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' type: string type: object + required: + - apiSecretRef + - path + - repoName type: object type: object gitRepoURL: description: GitRepoURL git repository storing the tenant configuration. If this is set, no gitRepoTemplate is needed. type: string + required: + - displayName type: object status: description: TenantStatus defines the observed state of Tenant diff --git a/deploy/crds/syn.tools_v1alpha1_cluster_cr.yaml b/deploy/crds/syn.tools_v1alpha1_cluster_cr.yaml index a50c2014..e4dc539e 100644 --- a/deploy/crds/syn.tools_v1alpha1_cluster_cr.yaml +++ b/deploy/crds/syn.tools_v1alpha1_cluster_cr.yaml @@ -2,28 +2,27 @@ apiVersion: syn.tools/v1alpha1 kind: Cluster metadata: name: ae3oso - labels: - tenant: aezoo6 + # labels: + # tenant: aezoo6 annotations: tenantslug: bigcorp spec: - apiEndpointSecretRef: - key: k8sapi-ae3oso - name: default displayName: Big Corp. Production Cluster slug: prodcluster gitRepoTemplate: spec: + path: test + repoName: test apiSecretRef: key: my-api-secret name: default deployKeys: - - type: ssh-ed25519 - key: AAAA... - writeAccess: true + test: + type: ssh-ed25519 + key: AAAA... + writeAccess: true tenantRef: name: aezoo6 - namespace: default tokenLifeTime: 4h facts: distribution: openshift3 diff --git a/deploy/crds/syn.tools_v1alpha1_gitrepo_cr.yaml b/deploy/crds/syn.tools_v1alpha1_gitrepo_cr.yaml index cd3ffe9a..be971188 100644 --- a/deploy/crds/syn.tools_v1alpha1_gitrepo_cr.yaml +++ b/deploy/crds/syn.tools_v1alpha1_gitrepo_cr.yaml @@ -3,5 +3,7 @@ kind: GitRepo metadata: name: example-gitrepo spec: - # Add fields here - size: 3 + apiSecretRef: + name: test + path: test + repoName: test diff --git a/deploy/crds/syn.tools_v1alpha1_tenant_cr.yaml b/deploy/crds/syn.tools_v1alpha1_tenant_cr.yaml index 2e5ade57..b5bcc547 100644 --- a/deploy/crds/syn.tools_v1alpha1_tenant_cr.yaml +++ b/deploy/crds/syn.tools_v1alpha1_tenant_cr.yaml @@ -7,10 +7,17 @@ spec: displayName: Big Corp. gitRepoTemplate: spec: + path: test + repoName: test apiSecretRef: key: my-api-secret - name: syn-lieutenant + name: default deployKeys: - - type: ssh-ed25519 - key: AAAA... - writeAccess: true + test: + type: ssh-ed25519 + key: AAAA... + writeAccess: true + test2: + type: ssh-rsa + key: AAAA... + writeAccess: false diff --git a/go.mod b/go.mod index 01a59342..11de203f 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,10 @@ module github.com/projectsyn/lieutenant-operator go 1.13 require ( - github.com/go-logr/logr v0.1.0 + github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e github.com/operator-framework/operator-sdk v0.14.0 github.com/spf13/pflag v1.0.5 + github.com/stretchr/testify v1.4.0 k8s.io/api v0.0.0 k8s.io/apimachinery v0.0.0 k8s.io/client-go v12.0.0+incompatible @@ -39,3 +40,5 @@ replace ( ) replace github.com/docker/docker => github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309 // Required by Helm + +replace github.com/stretchr/testify => github.com/stretchr/testify v1.4.0 diff --git a/go.sum b/go.sum index 4516c387..c1b8779e 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,7 @@ github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEg github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= @@ -114,10 +115,12 @@ github.com/coredns/corefile-migration v1.0.2/go.mod h1:OFwBp/Wc9dJt5cAZzHWMNhK1r github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.15+incompatible h1:+9RjdC18gMxNQVvSiXvObLu29mOFmkgdsB4cRTlV+EE= github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -189,6 +192,7 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/structtag v1.1.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk= github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= @@ -296,8 +300,11 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450 h1:7xqw01UYS+KCI25bMrPxwNYkSns2Db1ziQPpVq99FpE= github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= +github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995 h1:f5gsjBiF9tRRVomCvrkGMMWI8W1f2OBFar2c5oakAP0= github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8= +github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e h1:KhcknUwkWHKZPbFy2P7jH5LKJ3La+0ZeknkkmrSgqb0= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -401,9 +408,11 @@ github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kshvakov/clickhouse v1.3.5/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= @@ -606,9 +615,6 @@ github.com/storageos/go-api v0.0.0-20180912212459-343b3eff91fc/go.mod h1:ZrLn+e0 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/syndtr/gocapability v0.0.0-20160928074757-e7cb7fa329f4/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -802,6 +808,7 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190128161407-8ac453e89fca/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= @@ -823,6 +830,7 @@ gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= diff --git a/pkg/apis/constants.go b/pkg/apis/constants.go new file mode 100644 index 00000000..ab0a10ba --- /dev/null +++ b/pkg/apis/constants.go @@ -0,0 +1,5 @@ +package apis + +const ( + LabelNameTenant = "syn.tools/tenant" +) diff --git a/pkg/apis/syn/v1alpha1/cluster_types.go b/pkg/apis/syn/v1alpha1/cluster_types.go index 4bc1d7a5..975fc68b 100644 --- a/pkg/apis/syn/v1alpha1/cluster_types.go +++ b/pkg/apis/syn/v1alpha1/cluster_types.go @@ -2,31 +2,22 @@ package v1alpha1 import ( corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// FactKey is the key for the facts map -type FactKey string - -// FactValue is the value for the facts map -type FactValue string - // Facts is a map of arbitrary facts for the cluster -type Facts map[FactKey]FactValue +type Facts map[string]string // ClusterSpec defines the desired state of Cluster type ClusterSpec struct { - // APIEndpointSecretRef references the secret containing connection information to Kubernetes API endpoint of the registered Kubernetes cluster. - APIEndpointSecretRef *corev1.SecretReference `json:"apiEndpointSecretRef,omitempty"` // DisplayName of cluster which could be different from metadata.name. Allows cluster renaming should it be needed. - DisplayName string `json:"displayName,omitempty"` + DisplayName string `json:"displayName"` // GitRepoURL git repository storing the cluster configuration catalog. If this is set, no gitRepoTemplate is needed. GitRepoURL string `json:"gitRepoURL,omitempty"` // GitRepoTemplate template for managing the GitRepo object. GitRepoTemplate *GitRepoTemplate `json:"gitRepoTemplate,omitempty"` // TenantRef reference to Tenant object the cluster belongs to. - TenantRef TenantRef `json:"tenantRef,omitempty"` + TenantRef *corev1.LocalObjectReference `json:"tenantRef"` // TokenLifetime set the token lifetime TokenLifeTime string `json:"tokenLifeTime,omitempty"` // Facts are key/value pairs for statically configured facts @@ -51,6 +42,8 @@ type ClusterStatus struct { // Cluster is the Schema for the clusters API // +kubebuilder:subresource:status // +kubebuilder:resource:path=clusters,scope=Namespaced +// +kubebuilder:printcolumn:name="Display Name",type="string",JSONPath=".spec.displayName" +// +kubebuilder:printcolumn:name="Tenant",type="string",JSONPath=".spec.tenantRef.name" type Cluster struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/pkg/apis/syn/v1alpha1/gitrepo_types.go b/pkg/apis/syn/v1alpha1/gitrepo_types.go index ddaf5ff3..aa4e6147 100644 --- a/pkg/apis/syn/v1alpha1/gitrepo_types.go +++ b/pkg/apis/syn/v1alpha1/gitrepo_types.go @@ -37,15 +37,15 @@ func (g GitType) IsValid() bool { // GitRepoSpec defines the desired state of GitRepo type GitRepoSpec struct { // APISecretRef reference to secret containing connection information - APISecretRef *corev1.SecretReference `json:"apiSecretRef,omitempty"` + APISecretRef *corev1.SecretReference `json:"apiSecretRef"` // DeployKeys optional list of SSH deploy keys. If not set, not deploy keys will be configured - DeployKeys []DeployKey `json:"deployKeys,omitempty"` + DeployKeys map[string]DeployKey `json:"deployKeys,omitempty"` // Path to Git repository - Path string `json:"path,omitempty"` + Path string `json:"path"` // RepoName ame of Git repository - RepoName string `json:"repoName,omitempty"` + RepoName string `json:"repoName"` // TenantRef references the tenant this repo belongs to - TenantRef *TenantRef `json:"tenantRef,omitempty"` + TenantRef *corev1.LocalObjectReference `json:"tenantRef,omitempty"` } // DeployKey defines an SSH key to be used for git operations. @@ -86,6 +86,8 @@ type GitRepoTemplate struct { // GitRepo is the Schema for the gitrepos API // +kubebuilder:subresource:status // +kubebuilder:resource:path=gitrepos,scope=Namespaced +// +kubebuilder:printcolumn:name="Repo Name",type="string",JSONPath=".spec.repoName" +// +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase" type GitRepo struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/pkg/apis/syn/v1alpha1/tenant_types.go b/pkg/apis/syn/v1alpha1/tenant_types.go index 4cd04153..0b9c983d 100644 --- a/pkg/apis/syn/v1alpha1/tenant_types.go +++ b/pkg/apis/syn/v1alpha1/tenant_types.go @@ -7,7 +7,7 @@ import ( // TenantSpec defines the desired state of Tenant type TenantSpec struct { // DisplayName is the display name of the tenant. - DisplayName string `json:"displayName,omitempty"` + DisplayName string `json:"displayName"` // GitRepoURL git repository storing the tenant configuration. If this is set, no gitRepoTemplate is needed. GitRepoURL string `json:"gitRepoURL,omitempty"` // GitRepoTemplate Template for managing the GitRepo object. If not set, no GitRepo object will be created. @@ -19,17 +19,12 @@ type TenantStatus struct { // TBD } -// TenantRef contains a reference to a tenant, for use in other CRDs -type TenantRef struct { - Name string `json:"name,omitempty"` - Namespace string `json:"namespace,omitempty"` -} - // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // Tenant is the Schema for the tenants API // +kubebuilder:subresource:status // +kubebuilder:resource:path=tenants,scope=Namespaced +// +kubebuilder:printcolumn:name="Display Name",type="string",JSONPath=".spec.displayName" type Tenant struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/pkg/apis/syn/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/syn/v1alpha1/zz_generated.deepcopy.go index 2d4506a2..67f16030 100644 --- a/pkg/apis/syn/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/syn/v1alpha1/zz_generated.deepcopy.go @@ -90,23 +90,22 @@ func (in *ClusterList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) { *out = *in - if in.APIEndpointSecretRef != nil { - in, out := &in.APIEndpointSecretRef, &out.APIEndpointSecretRef - *out = new(v1.SecretReference) - **out = **in - } if in.GitRepoTemplate != nil { in, out := &in.GitRepoTemplate, &out.GitRepoTemplate *out = new(GitRepoTemplate) (*in).DeepCopyInto(*out) } - out.TenantRef = in.TenantRef + if in.TenantRef != nil { + in, out := &in.TenantRef, &out.TenantRef + *out = new(v1.LocalObjectReference) + **out = **in + } if in.Facts != nil { in, out := &in.Facts, &out.Facts *out = new(Facts) if **in != nil { in, out := *in, *out - *out = make(map[FactKey]FactValue, len(*in)) + *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val } @@ -272,12 +271,14 @@ func (in *GitRepoSpec) DeepCopyInto(out *GitRepoSpec) { } if in.DeployKeys != nil { in, out := &in.DeployKeys, &out.DeployKeys - *out = make([]DeployKey, len(*in)) - copy(*out, *in) + *out = make(map[string]DeployKey, len(*in)) + for key, val := range *in { + (*out)[key] = val + } } if in.TenantRef != nil { in, out := &in.TenantRef, &out.TenantRef - *out = new(TenantRef) + *out = new(v1.LocalObjectReference) **out = **in } return @@ -394,22 +395,6 @@ func (in *TenantList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TenantRef) DeepCopyInto(out *TenantRef) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantRef. -func (in *TenantRef) DeepCopy() *TenantRef { - if in == nil { - return nil - } - out := new(TenantRef) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { *out = *in diff --git a/pkg/controller/cluster/cluster_controller.go b/pkg/controller/cluster/cluster_controller.go index b9834f42..bdbf2c37 100644 --- a/pkg/controller/cluster/cluster_controller.go +++ b/pkg/controller/cluster/cluster_controller.go @@ -44,7 +44,11 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { return err } - return nil + return c.Watch(&source.Kind{Type: &synv1alpha1.GitRepo{}}, &handler.EnqueueRequestForOwner{ + IsController: true, + OwnerType: &synv1alpha1.Cluster{}, + }) + } // blank assignment to verify that ReconcileCluster implements reconcile.Reconciler diff --git a/pkg/controller/cluster/cluster_reconcile.go b/pkg/controller/cluster/cluster_reconcile.go index ae6ac283..94dbb926 100644 --- a/pkg/controller/cluster/cluster_reconcile.go +++ b/pkg/controller/cluster/cluster_reconcile.go @@ -6,11 +6,14 @@ import ( "encoding/base64" "time" + "github.com/projectsyn/lieutenant-operator/pkg/apis" + synv1alpha1 "github.com/projectsyn/lieutenant-operator/pkg/apis/syn/v1alpha1" "github.com/projectsyn/lieutenant-operator/pkg/helpers" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -22,18 +25,11 @@ func (r *ReconcileCluster) Reconcile(request reconcile.Request) (reconcile.Resul reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) reqLogger.Info("Reconciling Cluster") - // Fetch the Cluster instance instance := &synv1alpha1.Cluster{} - err := r.client.Get(context.TODO(), request.NamespacedName, instance) + + res, err := r.fetchInstance(instance, request.NamespacedName) if err != nil { - if errors.IsNotFound(err) { - // Request object not found, could have been deleted after reconcile request. - // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. - // Return and don't requeue - return reconcile.Result{}, nil - } - // Error reading the object - requeue the request. - return reconcile.Result{}, err + return res, err } if instance.Status.BootstrapToken == nil { @@ -50,14 +46,31 @@ func (r *ReconcileCluster) Reconcile(request reconcile.Request) (reconcile.Resul Kind: instance.Kind, } - err = helpers.CreateGitRepo(instance, gvk, instance.Spec.GitRepoTemplate, r.client, &instance.Spec.TenantRef) + err := helpers.CreateGitRepo(instance, gvk, instance.Spec.GitRepoTemplate, r.client, instance.Spec.TenantRef) if err != nil { reqLogger.Error(err, "Cannot create git repo object") return reconcile.Result{}, err } + + gitRepo := &synv1alpha1.GitRepo{} + repoNamespacedName := types.NamespacedName{ + Namespace: instance.GetNamespace(), + Name: helpers.GetRepoName(instance.Spec.TenantRef.Name, gvk), + } + err = r.client.Get(context.TODO(), repoNamespacedName, gitRepo) + if err != nil { + return reconcile.Result{}, err + } + + if gitRepo.Status.Phase == synv1alpha1.Created { + instance.Spec.GitRepoURL = gitRepo.Status.URL + } + } - return reconcile.Result{}, nil + r.addLabels(instance) + + return reconcile.Result{}, r.client.Update(context.TODO(), instance) } func (r *ReconcileCluster) generateToken() (string, error) { @@ -103,3 +116,27 @@ func (r *ReconcileCluster) updateStatus(cluster *synv1alpha1.Cluster) error { return r.client.Status().Update(context.TODO(), cluster) } + +func (r *ReconcileCluster) addLabels(cluster *synv1alpha1.Cluster) { + + if cluster.ObjectMeta.Labels == nil { + cluster.ObjectMeta.Labels = make(map[string]string) + } + cluster.ObjectMeta.Labels[apis.LabelNameTenant] = cluster.Spec.TenantRef.Name +} + +func (r *ReconcileCluster) fetchInstance(instance *synv1alpha1.Cluster, namespacedName types.NamespacedName) (reconcile.Result, error) { + + err := r.client.Get(context.TODO(), namespacedName, instance) + if err != nil { + if errors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. + // Return and don't requeue + return reconcile.Result{}, err + } + // Error reading the object - requeue the request. + return reconcile.Result{Requeue: true}, err + } + return reconcile.Result{}, nil +} diff --git a/pkg/controller/cluster/cluster_reconcile_test.go b/pkg/controller/cluster/cluster_reconcile_test.go new file mode 100644 index 00000000..4a533948 --- /dev/null +++ b/pkg/controller/cluster/cluster_reconcile_test.go @@ -0,0 +1,118 @@ +package cluster + +import ( + "context" + corev1 "k8s.io/api/core/v1" + "reflect" + "testing" + + "github.com/projectsyn/lieutenant-operator/pkg/apis" + + "github.com/stretchr/testify/assert" + + synv1alpha1 "github.com/projectsyn/lieutenant-operator/pkg/apis/syn/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// testSetupClient returns a client containing all objects in objs +func testSetupClient(objs []runtime.Object) (client.Client, *runtime.Scheme) { + s := scheme.Scheme + s.AddKnownTypes(synv1alpha1.SchemeGroupVersion, objs...) + return fake.NewFakeClient(objs...), s +} + +func TestReconcileCluster_Reconcile(t *testing.T) { + type fields struct { + tenantName string + objName string + objNamespace string + } + tests := []struct { + name string + want reconcile.Result + wantErr bool + fields fields + }{ + { + name: "Check cluster state after creation", + want: reconcile.Result{}, + wantErr: false, + fields: fields{ + tenantName: "test-tenant", + objName: "test-object", + objNamespace: "tenant", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + tenant := &synv1alpha1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: tt.fields.objName, + Namespace: tt.fields.objNamespace, + }, + Spec: synv1alpha1.ClusterSpec{ + DisplayName: "test", + GitRepoTemplate: &synv1alpha1.GitRepoTemplate{ + Spec: synv1alpha1.GitRepoSpec{ + RepoName: "test", + }, + }, + TenantRef: &corev1.LocalObjectReference{ + Name: tt.fields.tenantName, + }, + }, + } + + objs := []runtime.Object{ + tenant, + &synv1alpha1.GitRepo{}, + } + + cl, s := testSetupClient(objs) + + r := &ReconcileCluster{client: cl, scheme: s} + + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: tt.fields.objName, + Namespace: tt.fields.objNamespace, + }, + } + + got, err := r.Reconcile(req) + if (err != nil) != tt.wantErr { + t.Errorf("Reconcile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Reconcile() got = %v, want %v", got, tt.want) + } + + gitRepoNamespacedName := types.NamespacedName{ + Name: tt.fields.tenantName + "-cluster", + Namespace: tt.fields.objNamespace, + } + + gitRepo := &synv1alpha1.GitRepo{} + err = cl.Get(context.TODO(), gitRepoNamespacedName, gitRepo) + assert.NoError(t, err) + + newCluster := &synv1alpha1.Cluster{} + err = cl.Get(context.TODO(), req.NamespacedName, newCluster) + assert.NoError(t, err) + + assert.Equal(t, tt.fields.tenantName, newCluster.Labels[apis.LabelNameTenant]) + + assert.NotEmpty(t, newCluster.Status.BootstrapToken.Token) + + }) + } +} diff --git a/pkg/controller/gitrepo/gitrepo_reconcile.go b/pkg/controller/gitrepo/gitrepo_reconcile.go index 687eeb02..1fe95b5c 100644 --- a/pkg/controller/gitrepo/gitrepo_reconcile.go +++ b/pkg/controller/gitrepo/gitrepo_reconcile.go @@ -10,9 +10,6 @@ import ( // Reconcile reads that state of the cluster for a GitRepo object and makes changes based on the state read // and what is in the GitRepo.Spec -// TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates -// a Pod as an example -// Note: // The Controller will requeue the Request to be processed again if the returned error is non-nil or // Result.Requeue is true, otherwise upon completion it will remove the work from the queue. func (r *ReconcileGitRepo) Reconcile(request reconcile.Request) (reconcile.Result, error) { diff --git a/pkg/controller/tenant/tenant_controller.go b/pkg/controller/tenant/tenant_controller.go index f08e1426..9facefbb 100644 --- a/pkg/controller/tenant/tenant_controller.go +++ b/pkg/controller/tenant/tenant_controller.go @@ -39,7 +39,11 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { return err } - return nil + return c.Watch(&source.Kind{Type: &synv1alpha1.GitRepo{}}, &handler.EnqueueRequestForOwner{ + IsController: true, + OwnerType: &synv1alpha1.Tenant{}, + }) + } // blank assignment to verify that ReconcileTenant implements reconcile.Reconciler diff --git a/pkg/controller/tenant/tenant_reconcile.go b/pkg/controller/tenant/tenant_reconcile.go index ae2758e8..3b572eb2 100644 --- a/pkg/controller/tenant/tenant_reconcile.go +++ b/pkg/controller/tenant/tenant_reconcile.go @@ -7,14 +7,10 @@ import ( "github.com/projectsyn/lieutenant-operator/pkg/helpers" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) -// Reconcile reads that state of the cluster for a Tenant object and makes changes based on the state read -// and what is in the Tenant.Spec -// TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates -// a Pod as an example -// Note: // The Controller will requeue the Request to be processed again if the returned error is non-nil or // Result.Requeue is true, otherwise upon completion it will remove the work from the queue. func (r *ReconcileTenant) Reconcile(request reconcile.Request) (reconcile.Result, error) { @@ -43,9 +39,24 @@ func (r *ReconcileTenant) Reconcile(request reconcile.Request) (reconcile.Result err = helpers.CreateGitRepo(instance, gvk, instance.Spec.GitRepoTemplate, r.client, nil) if err != nil { - reqLogger.Error(err, "Cannot create git repo object") + return reconcile.Result{}, err } + + gitRepo := &synv1alpha1.GitRepo{} + repoNamespacedName := types.NamespacedName{ + Namespace: instance.GetNamespace(), + Name: helpers.GetRepoName(instance.GetName(), gvk), + } + err = r.client.Get(context.TODO(), repoNamespacedName, gitRepo) + if err != nil { + return reconcile.Result{}, err + } + + if gitRepo.Status.Phase == synv1alpha1.Created { + instance.Spec.GitRepoURL = gitRepo.Status.URL + } + } - return reconcile.Result{}, nil + return reconcile.Result{}, r.client.Update(context.TODO(), instance) } diff --git a/pkg/controller/tenant/tenant_reconcile_test.go b/pkg/controller/tenant/tenant_reconcile_test.go new file mode 100644 index 00000000..9025d994 --- /dev/null +++ b/pkg/controller/tenant/tenant_reconcile_test.go @@ -0,0 +1,96 @@ +package tenant + +import ( + "context" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + + synv1alpha1 "github.com/projectsyn/lieutenant-operator/pkg/apis/syn/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +func testSetupClient(objs []runtime.Object) (client.Client, *runtime.Scheme) { + s := scheme.Scheme + s.AddKnownTypes(synv1alpha1.SchemeGroupVersion, objs...) + return fake.NewFakeClient(objs...), s +} + +func TestCreateGitRepo(t *testing.T) { + tests := []struct { + name string + want reconcile.Result + wantErr bool + objName string + namespace string + }{ + { + name: "Git repo object created", + want: reconcile.Result{}, + wantErr: false, + objName: "test", + namespace: "tenant", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + tenant := &synv1alpha1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: tt.objName, + Namespace: tt.namespace, + }, + Spec: synv1alpha1.TenantSpec{ + DisplayName: "test", + GitRepoTemplate: &synv1alpha1.GitRepoTemplate{ + Spec: synv1alpha1.GitRepoSpec{ + RepoName: "test", + }, + }, + }, + } + + objs := []runtime.Object{ + tenant, + &synv1alpha1.GitRepo{}, + } + + cl, s := testSetupClient(objs) + + r := &ReconcileTenant{client: cl, scheme: s} + + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: tt.objName, + Namespace: tt.namespace, + }, + } + + got, err := r.Reconcile(req) + if (err != nil) != tt.wantErr { + t.Errorf("Reconcile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Reconcile() got = %v, want %v", got, tt.want) + } + + gitRepoNamespacedName := types.NamespacedName{ + Name: tt.objName + "-tenant", + Namespace: tt.namespace, + } + + gitRepo := &synv1alpha1.GitRepo{} + err = cl.Get(context.TODO(), gitRepoNamespacedName, gitRepo) + assert.NoError(t, err) + + }) + } +} diff --git a/pkg/helpers/crd.go b/pkg/helpers/crd.go index 7264d4b6..7c83eb08 100644 --- a/pkg/helpers/crd.go +++ b/pkg/helpers/crd.go @@ -3,6 +3,7 @@ package helpers import ( "context" "fmt" + corev1 "k8s.io/api/core/v1" "strings" synv1alpha1 "github.com/projectsyn/lieutenant-operator/pkg/apis/syn/v1alpha1" @@ -14,20 +15,23 @@ import ( ) // CreateGitRepo will create the gitRepo object if it doesn't already exist. If the owner object itself is a tenant tenantRef can be set nil. -func CreateGitRepo(obj metav1.Object, gvk schema.GroupVersionKind, template *synv1alpha1.GitRepoTemplate, client client.Client, tenantRef *synv1alpha1.TenantRef) error { +func CreateGitRepo(obj metav1.Object, gvk schema.GroupVersionKind, template *synv1alpha1.GitRepoTemplate, client client.Client, tenantRef *corev1.LocalObjectReference) error { + + if template == nil { + return fmt.Errorf("gitRepo template is empty") + } tenantName := obj.GetName() tenantNamespace := obj.GetNamespace() if tenantRef != nil { template.Spec.TenantRef = tenantRef tenantName = tenantRef.Name - tenantNamespace = tenantRef.Namespace + tenantNamespace = obj.GetNamespace() } repo := &synv1alpha1.GitRepo{ ObjectMeta: metav1.ObjectMeta{ - // Names have to be lowercase - Name: strings.ToLower(tenantName + "-" + gvk.Kind), + Name: GetRepoName(tenantName, gvk), Namespace: tenantNamespace, OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef(obj, gvk), @@ -58,3 +62,8 @@ func CreateGitRepo(obj metav1.Object, gvk schema.GroupVersionKind, template *syn } return nil } + +// GetRepoName will return the stable repo name for a given parent f.e. Cluster or Tenant +func GetRepoName(tenantName string, gvk schema.GroupVersionKind) string { + return strings.ToLower(tenantName + "-" + gvk.Kind) +} From 2802d5d0631993e1ff10ef01a02b702e7ec6448f Mon Sep 17 00:00:00 2001 From: Simon Beck Date: Wed, 5 Feb 2020 09:01:05 +0100 Subject: [PATCH 2/2] CRD adjustments --- deploy/crds/syn.tools_clusters_crd.yaml | 80 ++++++++----------- deploy/crds/syn.tools_gitrepos_crd.yaml | 73 +++++++++-------- deploy/crds/syn.tools_tenants_crd.yaml | 80 ++++++++----------- .../crds/syn.tools_v1alpha1_cluster_cr.yaml | 21 +++-- .../crds/syn.tools_v1alpha1_gitrepo_cr.yaml | 11 ++- deploy/crds/syn.tools_v1alpha1_tenant_cr.yaml | 29 ++++--- pkg/apis/syn/v1alpha1/cluster_types.go | 2 +- pkg/apis/syn/v1alpha1/gitrepo_types.go | 17 ++-- .../syn/v1alpha1/zz_generated.deepcopy.go | 35 +++----- pkg/controller/cluster/cluster_reconcile.go | 2 +- .../cluster/cluster_reconcile_test.go | 6 +- .../tenant/tenant_reconcile_test.go | 4 +- pkg/helpers/crd.go | 8 +- 13 files changed, 169 insertions(+), 199 deletions(-) diff --git a/deploy/crds/syn.tools_clusters_crd.yaml b/deploy/crds/syn.tools_clusters_crd.yaml index 8f13dfa2..422780b5 100644 --- a/deploy/crds/syn.tools_clusters_crd.yaml +++ b/deploy/crds/syn.tools_clusters_crd.yaml @@ -50,57 +50,43 @@ spec: gitRepoTemplate: description: GitRepoTemplate template for managing the GitRepo object. properties: - spec: - description: GitRepoSpec defines the desired state of GitRepo + apiSecretRef: + description: APISecretRef reference to secret containing connection + information properties: - apiSecretRef: - description: APISecretRef reference to secret containing connection - information - properties: - name: - description: Name is unique within a namespace to reference - a secret resource. - type: string - namespace: - description: Namespace defines the space within which the - secret name must be unique. - type: string - type: object - deployKeys: - additionalProperties: - description: DeployKey defines an SSH key to be used for git - operations. - properties: - key: - type: string - type: - type: string - writeAccess: - type: boolean - type: object - description: DeployKeys optional list of SSH deploy keys. If - not set, not deploy keys will be configured - type: object - path: - description: Path to Git repository + name: + description: Name is unique within a namespace to reference + a secret resource. type: string - repoName: - description: RepoName ame of Git repository + namespace: + description: Namespace defines the space within which the secret + name must be unique. type: string - tenantRef: - description: TenantRef references the tenant this repo belongs - to - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - type: object - required: - - apiSecretRef - - path - - repoName type: object + deployKeys: + additionalProperties: + description: DeployKey defines an SSH key to be used for git operations. + properties: + key: + type: string + type: + type: string + writeAccess: + type: boolean + type: object + description: DeployKeys optional list of SSH deploy keys. If not + set, not deploy keys will be configured + type: object + path: + description: Path to Git repository + type: string + repoName: + description: RepoName ame of Git repository + type: string + required: + - apiSecretRef + - path + - repoName type: object gitRepoURL: description: GitRepoURL git repository storing the cluster configuration diff --git a/deploy/crds/syn.tools_gitrepos_crd.yaml b/deploy/crds/syn.tools_gitrepos_crd.yaml index 4e3e1def..ef1638b8 100644 --- a/deploy/crds/syn.tools_gitrepos_crd.yaml +++ b/deploy/crds/syn.tools_gitrepos_crd.yaml @@ -38,39 +38,49 @@ spec: spec: description: GitRepoSpec defines the desired state of GitRepo properties: - apiSecretRef: - description: APISecretRef reference to secret containing connection - information + gitRepoTemplate: + description: GitRepoTemplate is used for templating git repos, it does + not contain the tenantRef as it will be added by the controller creating + the template instance. properties: - name: - description: Name is unique within a namespace to reference a secret - resource. + apiSecretRef: + description: APISecretRef reference to secret containing connection + information + properties: + name: + description: Name is unique within a namespace to reference + a secret resource. + type: string + namespace: + description: Namespace defines the space within which the secret + name must be unique. + type: string + type: object + deployKeys: + additionalProperties: + description: DeployKey defines an SSH key to be used for git operations. + properties: + key: + type: string + type: + type: string + writeAccess: + type: boolean + type: object + description: DeployKeys optional list of SSH deploy keys. If not + set, not deploy keys will be configured + type: object + path: + description: Path to Git repository type: string - namespace: - description: Namespace defines the space within which the secret - name must be unique. + repoName: + description: RepoName ame of Git repository type: string + required: + - apiSecretRef + - path + - repoName type: object - deployKeys: - additionalProperties: - description: DeployKey defines an SSH key to be used for git operations. - properties: - key: - type: string - type: - type: string - writeAccess: - type: boolean - type: object - description: DeployKeys optional list of SSH deploy keys. If not set, - not deploy keys will be configured - type: object - path: - description: Path to Git repository - type: string - repoName: - description: RepoName ame of Git repository - type: string tenantRef: description: TenantRef references the tenant this repo belongs to properties: @@ -80,9 +90,8 @@ spec: type: string type: object required: - - apiSecretRef - - path - - repoName + - gitRepoTemplate + - tenantRef type: object status: description: GitRepoStatus defines the observed state of GitRepo diff --git a/deploy/crds/syn.tools_tenants_crd.yaml b/deploy/crds/syn.tools_tenants_crd.yaml index bc209e11..3011d38f 100644 --- a/deploy/crds/syn.tools_tenants_crd.yaml +++ b/deploy/crds/syn.tools_tenants_crd.yaml @@ -42,57 +42,43 @@ spec: description: GitRepoTemplate Template for managing the GitRepo object. If not set, no GitRepo object will be created. properties: - spec: - description: GitRepoSpec defines the desired state of GitRepo + apiSecretRef: + description: APISecretRef reference to secret containing connection + information properties: - apiSecretRef: - description: APISecretRef reference to secret containing connection - information - properties: - name: - description: Name is unique within a namespace to reference - a secret resource. - type: string - namespace: - description: Namespace defines the space within which the - secret name must be unique. - type: string - type: object - deployKeys: - additionalProperties: - description: DeployKey defines an SSH key to be used for git - operations. - properties: - key: - type: string - type: - type: string - writeAccess: - type: boolean - type: object - description: DeployKeys optional list of SSH deploy keys. If - not set, not deploy keys will be configured - type: object - path: - description: Path to Git repository + name: + description: Name is unique within a namespace to reference + a secret resource. type: string - repoName: - description: RepoName ame of Git repository + namespace: + description: Namespace defines the space within which the secret + name must be unique. type: string - tenantRef: - description: TenantRef references the tenant this repo belongs - to - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - type: object - required: - - apiSecretRef - - path - - repoName type: object + deployKeys: + additionalProperties: + description: DeployKey defines an SSH key to be used for git operations. + properties: + key: + type: string + type: + type: string + writeAccess: + type: boolean + type: object + description: DeployKeys optional list of SSH deploy keys. If not + set, not deploy keys will be configured + type: object + path: + description: Path to Git repository + type: string + repoName: + description: RepoName ame of Git repository + type: string + required: + - apiSecretRef + - path + - repoName type: object gitRepoURL: description: GitRepoURL git repository storing the tenant configuration. diff --git a/deploy/crds/syn.tools_v1alpha1_cluster_cr.yaml b/deploy/crds/syn.tools_v1alpha1_cluster_cr.yaml index e4dc539e..058f1981 100644 --- a/deploy/crds/syn.tools_v1alpha1_cluster_cr.yaml +++ b/deploy/crds/syn.tools_v1alpha1_cluster_cr.yaml @@ -10,17 +10,16 @@ spec: displayName: Big Corp. Production Cluster slug: prodcluster gitRepoTemplate: - spec: - path: test - repoName: test - apiSecretRef: - key: my-api-secret - name: default - deployKeys: - test: - type: ssh-ed25519 - key: AAAA... - writeAccess: true + path: test + repoName: test + apiSecretRef: + key: my-api-secret + name: default + deployKeys: + test: + type: ssh-ed25519 + key: AAAA... + writeAccess: true tenantRef: name: aezoo6 tokenLifeTime: 4h diff --git a/deploy/crds/syn.tools_v1alpha1_gitrepo_cr.yaml b/deploy/crds/syn.tools_v1alpha1_gitrepo_cr.yaml index be971188..7cf9397a 100644 --- a/deploy/crds/syn.tools_v1alpha1_gitrepo_cr.yaml +++ b/deploy/crds/syn.tools_v1alpha1_gitrepo_cr.yaml @@ -3,7 +3,10 @@ kind: GitRepo metadata: name: example-gitrepo spec: - apiSecretRef: - name: test - path: test - repoName: test + gitRepoTemplate: + apiSecretRef: + name: test + path: test + repoName: test + tenantRef: + name: test diff --git a/deploy/crds/syn.tools_v1alpha1_tenant_cr.yaml b/deploy/crds/syn.tools_v1alpha1_tenant_cr.yaml index b5bcc547..92c493c9 100644 --- a/deploy/crds/syn.tools_v1alpha1_tenant_cr.yaml +++ b/deploy/crds/syn.tools_v1alpha1_tenant_cr.yaml @@ -6,18 +6,17 @@ spec: slug: bigcorp displayName: Big Corp. gitRepoTemplate: - spec: - path: test - repoName: test - apiSecretRef: - key: my-api-secret - name: default - deployKeys: - test: - type: ssh-ed25519 - key: AAAA... - writeAccess: true - test2: - type: ssh-rsa - key: AAAA... - writeAccess: false + path: test + repoName: test + apiSecretRef: + key: my-api-secret + name: default + deployKeys: + test: + type: ssh-ed25519 + key: AAAA... + writeAccess: true + test2: + type: ssh-rsa + key: AAAA... + writeAccess: false diff --git a/pkg/apis/syn/v1alpha1/cluster_types.go b/pkg/apis/syn/v1alpha1/cluster_types.go index 975fc68b..8a8ad8af 100644 --- a/pkg/apis/syn/v1alpha1/cluster_types.go +++ b/pkg/apis/syn/v1alpha1/cluster_types.go @@ -17,7 +17,7 @@ type ClusterSpec struct { // GitRepoTemplate template for managing the GitRepo object. GitRepoTemplate *GitRepoTemplate `json:"gitRepoTemplate,omitempty"` // TenantRef reference to Tenant object the cluster belongs to. - TenantRef *corev1.LocalObjectReference `json:"tenantRef"` + TenantRef corev1.LocalObjectReference `json:"tenantRef"` // TokenLifetime set the token lifetime TokenLifeTime string `json:"tokenLifeTime,omitempty"` // Facts are key/value pairs for statically configured facts diff --git a/pkg/apis/syn/v1alpha1/gitrepo_types.go b/pkg/apis/syn/v1alpha1/gitrepo_types.go index aa4e6147..00227303 100644 --- a/pkg/apis/syn/v1alpha1/gitrepo_types.go +++ b/pkg/apis/syn/v1alpha1/gitrepo_types.go @@ -36,16 +36,22 @@ func (g GitType) IsValid() bool { // GitRepoSpec defines the desired state of GitRepo type GitRepoSpec struct { + GitRepoTemplate `json:"gitRepoTemplate"` + // TenantRef references the tenant this repo belongs to + TenantRef corev1.LocalObjectReference `json:"tenantRef"` +} + +// GitRepoTemplate is used for templating git repos, it does not contain the tenantRef as it will be added by the +// controller creating the template instance. +type GitRepoTemplate struct { // APISecretRef reference to secret containing connection information - APISecretRef *corev1.SecretReference `json:"apiSecretRef"` + APISecretRef corev1.SecretReference `json:"apiSecretRef"` // DeployKeys optional list of SSH deploy keys. If not set, not deploy keys will be configured DeployKeys map[string]DeployKey `json:"deployKeys,omitempty"` // Path to Git repository Path string `json:"path"` // RepoName ame of Git repository RepoName string `json:"repoName"` - // TenantRef references the tenant this repo belongs to - TenantRef *corev1.LocalObjectReference `json:"tenantRef,omitempty"` } // DeployKey defines an SSH key to be used for git operations. @@ -76,11 +82,6 @@ type GitRepoConditions struct { Type string `json:"type,omitempty"` } -// GitRepoTemplate contains a GitRepoSpec for use in other CRDs -type GitRepoTemplate struct { - Spec GitRepoSpec `json:"spec,omitempty"` -} - // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // GitRepo is the Schema for the gitrepos API diff --git a/pkg/apis/syn/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/syn/v1alpha1/zz_generated.deepcopy.go index 67f16030..c87fd827 100644 --- a/pkg/apis/syn/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/syn/v1alpha1/zz_generated.deepcopy.go @@ -5,7 +5,6 @@ package v1alpha1 import ( - v1 "k8s.io/api/core/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -95,11 +94,7 @@ func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) { *out = new(GitRepoTemplate) (*in).DeepCopyInto(*out) } - if in.TenantRef != nil { - in, out := &in.TenantRef, &out.TenantRef - *out = new(v1.LocalObjectReference) - **out = **in - } + out.TenantRef = in.TenantRef if in.Facts != nil { in, out := &in.Facts, &out.Facts *out = new(Facts) @@ -264,23 +259,8 @@ func (in *GitRepoList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GitRepoSpec) DeepCopyInto(out *GitRepoSpec) { *out = *in - if in.APISecretRef != nil { - in, out := &in.APISecretRef, &out.APISecretRef - *out = new(v1.SecretReference) - **out = **in - } - if in.DeployKeys != nil { - in, out := &in.DeployKeys, &out.DeployKeys - *out = make(map[string]DeployKey, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.TenantRef != nil { - in, out := &in.TenantRef, &out.TenantRef - *out = new(v1.LocalObjectReference) - **out = **in - } + in.GitRepoTemplate.DeepCopyInto(&out.GitRepoTemplate) + out.TenantRef = in.TenantRef return } @@ -320,7 +300,14 @@ func (in *GitRepoStatus) DeepCopy() *GitRepoStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GitRepoTemplate) DeepCopyInto(out *GitRepoTemplate) { *out = *in - in.Spec.DeepCopyInto(&out.Spec) + out.APISecretRef = in.APISecretRef + if in.DeployKeys != nil { + in, out := &in.DeployKeys, &out.DeployKeys + *out = make(map[string]DeployKey, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } return } diff --git a/pkg/controller/cluster/cluster_reconcile.go b/pkg/controller/cluster/cluster_reconcile.go index 94dbb926..d7a5746c 100644 --- a/pkg/controller/cluster/cluster_reconcile.go +++ b/pkg/controller/cluster/cluster_reconcile.go @@ -46,7 +46,7 @@ func (r *ReconcileCluster) Reconcile(request reconcile.Request) (reconcile.Resul Kind: instance.Kind, } - err := helpers.CreateGitRepo(instance, gvk, instance.Spec.GitRepoTemplate, r.client, instance.Spec.TenantRef) + err := helpers.CreateGitRepo(instance, gvk, instance.Spec.GitRepoTemplate, r.client, &instance.Spec.TenantRef) if err != nil { reqLogger.Error(err, "Cannot create git repo object") return reconcile.Result{}, err diff --git a/pkg/controller/cluster/cluster_reconcile_test.go b/pkg/controller/cluster/cluster_reconcile_test.go index 4a533948..ac4d0abc 100644 --- a/pkg/controller/cluster/cluster_reconcile_test.go +++ b/pkg/controller/cluster/cluster_reconcile_test.go @@ -61,11 +61,9 @@ func TestReconcileCluster_Reconcile(t *testing.T) { Spec: synv1alpha1.ClusterSpec{ DisplayName: "test", GitRepoTemplate: &synv1alpha1.GitRepoTemplate{ - Spec: synv1alpha1.GitRepoSpec{ - RepoName: "test", - }, + RepoName: "test", }, - TenantRef: &corev1.LocalObjectReference{ + TenantRef: corev1.LocalObjectReference{ Name: tt.fields.tenantName, }, }, diff --git a/pkg/controller/tenant/tenant_reconcile_test.go b/pkg/controller/tenant/tenant_reconcile_test.go index 9025d994..aef973ca 100644 --- a/pkg/controller/tenant/tenant_reconcile_test.go +++ b/pkg/controller/tenant/tenant_reconcile_test.go @@ -50,9 +50,7 @@ func TestCreateGitRepo(t *testing.T) { Spec: synv1alpha1.TenantSpec{ DisplayName: "test", GitRepoTemplate: &synv1alpha1.GitRepoTemplate{ - Spec: synv1alpha1.GitRepoSpec{ - RepoName: "test", - }, + RepoName: "test", }, }, } diff --git a/pkg/helpers/crd.go b/pkg/helpers/crd.go index 7c83eb08..7c26e30a 100644 --- a/pkg/helpers/crd.go +++ b/pkg/helpers/crd.go @@ -24,9 +24,10 @@ func CreateGitRepo(obj metav1.Object, gvk schema.GroupVersionKind, template *syn tenantName := obj.GetName() tenantNamespace := obj.GetNamespace() if tenantRef != nil { - template.Spec.TenantRef = tenantRef tenantName = tenantRef.Name tenantNamespace = obj.GetNamespace() + } else { + tenantRef = &corev1.LocalObjectReference{} } repo := &synv1alpha1.GitRepo{ @@ -37,7 +38,10 @@ func CreateGitRepo(obj metav1.Object, gvk schema.GroupVersionKind, template *syn *metav1.NewControllerRef(obj, gvk), }, }, - Spec: template.Spec, + Spec: synv1alpha1.GitRepoSpec{ + GitRepoTemplate: *template, + TenantRef: *tenantRef, + }, } err := client.Create(context.TODO(), repo)