diff --git a/Makefile b/Makefile index 4fe6d83a..f18c48a7 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,3 @@ -#include test/e2e/Makefile - VERSION ?= 1.5.6 # check if we are using MacOS or LINUX and use that to determine the sed command @@ -109,10 +107,16 @@ fmt: ## Run go fmt against code. vet: ## Run go vet against code. go vet ./... -.PHONY: test -test: manifests generate fmt vet envtest ## Run test. +##@ Test + +.PHONY: e2e-test-without-cluster +e2e-test-without-cluster: manifests generate fmt vet envtest ## Run test. KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out +.PHONY: e2e-test-with-cluster +e2e-test-with-cluster: manifests generate fmt vet envtest ## Run test. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" ENVTEST_REMOTE=true go test ./... -coverprofile cover.out + ##@ Build .PHONY: build @@ -160,28 +164,28 @@ endif .PHONY: install install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. - $(KUSTOMIZE) build config/crd | kubectl apply -f - + $(KUSTOMIZE) build src/config/crd | kubectl apply -f - .PHONY: uninstall uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. - $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f - + $(KUSTOMIZE) build src/config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f - .PHONY: deploy deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. - cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} - $(KUSTOMIZE) build config/default | kubectl apply -f - + cd src/config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build src/config/default | kubectl apply -f - .PHONY: undeploy undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. - $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - + $(KUSTOMIZE) build src/config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - .PHONY: build-helm-chart build-helm-chart: manifests generate fmt vet kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. # update the crd - $(KUSTOMIZE) build config/crd > chart/templates/uffizziclusters.uffizzi.com_customresourcedefinition.yaml + $(KUSTOMIZE) build src/config/crd > chart/templates/uffizziclusters.uffizzi.com_customresourcedefinition.yaml $(SED) -i'' -e 's/labels:/labels: {{ include "common.labels.standard" . | nindent 4 }}/' chart/templates/uffizziclusters.uffizzi.com_customresourcedefinition.yaml # update roles - cp config/rbac/role.yaml chart/templates/manager-role_clusterrole.yaml + cp src/config/rbac/role.yaml chart/templates/manager-role_clusterrole.yaml $(SED) -i'' -e '/creationTimestamp: null/d' chart/templates/manager-role_clusterrole.yaml $(SED) -i'' -e 's/name: manager-role/name: {{ include "common.names.fullname" . }}-manager-role/' chart/templates/manager-role_clusterrole.yaml $(SED) -i'' -e 's/apiVersion: rbac.authorization.k8s.io\/v1/apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }}/' chart/templates/manager-role_clusterrole.yaml @@ -230,13 +234,13 @@ $(CONTROLLER_GEN): $(LOCALBIN) .PHONY: envtest envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. $(ENVTEST): $(LOCALBIN) - test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest + @test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest .PHONY: bundle bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. operator-sdk generate kustomize manifests -q - cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) - $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle $(BUNDLE_GEN_FLAGS) + cd src/config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) + $(KUSTOMIZE) build src/config/manifests | operator-sdk generate bundle $(BUNDLE_GEN_FLAGS) operator-sdk bundle validate ./bundle .PHONY: bundle-build diff --git a/chart/Chart.yaml b/chart/Chart.yaml index 6242ac72..fe8784de 100644 --- a/chart/Chart.yaml +++ b/chart/Chart.yaml @@ -10,7 +10,7 @@ version: 1.5.6 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "v1.5.5" +appVersion: "v1.5.6" dependencies: - name: common repository: https://charts.bitnami.com/bitnami diff --git a/chart/templates/manager-role_clusterrole.yaml b/chart/templates/manager-role_clusterrole.yaml index 55661338..a6b38a51 100644 --- a/chart/templates/manager-role_clusterrole.yaml +++ b/chart/templates/manager-role_clusterrole.yaml @@ -2,9 +2,7 @@ apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} kind: ClusterRole metadata: - labels: {{ include "common.labels.standard" . | nindent 4 }} - app.kubernetes.io/component: rbac - app.kubernetes.io/part-of: uffizzi +labels: {{ include "common.labels.standard" . | nindent 4 }} app.kubernetes.io/component: rbac app.kubernetes.io/part-of: uffizzi name: {{ include "common.names.fullname" . }}-manager-role rules: - apiGroups: diff --git a/chart/values.yaml b/chart/values.yaml index d3aadca8..c64fc9a9 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -4,7 +4,7 @@ image: repository: docker.io/uffizzi/uffizzi-cluster-operator - tag: v1.5.5 + tag: v1.5.6 # `flux` dependency values flux: helmController: diff --git a/config/crd/bases/uffizzi.com_uffizziclusters.yaml b/config/crd/bases/uffizzi.com_uffizziclusters.yaml new file mode 100644 index 00000000..0df7493c --- /dev/null +++ b/config/crd/bases/uffizzi.com_uffizziclusters.yaml @@ -0,0 +1,338 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: uffizziclusters.uffizzi.com +spec: + group: uffizzi.com + names: + kind: UffizziCluster + listKind: UffizziClusterList + plural: uffizziclusters + shortNames: + - uc + - ucluster + singular: uffizzicluster + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='APIReady')].status + name: APIReady + type: string + - jsonPath: .status.conditions[?(@.type=='DataStoreReady')].status + name: DataStoreReady + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=='Sleep')].status + name: Sleep + type: string + - jsonPath: .status.host + name: Host + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.lastAwakeTime + name: UptimeSinceLastAwake + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: UffizziCluster is the Schema for the UffizziClusters API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: UffizziClusterSpec defines the desired state of UffizziCluster + properties: + apiServer: + description: UffizziClusterAPIServer defines the API server capabilities + of the cluster + properties: + image: + type: string + type: object + distro: + default: k3s + enum: + - k3s + - k8s + type: string + externalDatastore: + default: sqlite + enum: + - etcd + - sqlite + type: string + helm: + items: + properties: + chart: + properties: + name: + type: string + repo: + type: string + version: + type: string + required: + - name + - repo + type: object + release: + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + values: + type: string + required: + - chart + - release + type: object + type: array + ingress: + description: UffiClusterIngress defines the ingress capabilities of + the cluster, the basic host can be setup for all + properties: + class: + type: string + host: + type: string + type: object + limitRange: + properties: + default: + properties: + cpu: + default: "0.5" + type: string + ephemeralStorage: + default: 8Gi + type: string + memory: + default: 1Gi + type: string + type: object + defaultRequest: + properties: + cpu: + default: "0.1" + type: string + ephemeralStorage: + default: 1Gi + type: string + memory: + default: 128Mi + type: string + type: object + enabled: + default: true + type: boolean + required: + - enabled + type: object + manifests: + type: string + resourceQuota: + description: UffizziClusterResourceQuota defines the resource quota + which defines the quota of resources a namespace has access to + properties: + count: + properties: + configMaps: + default: 20 + type: integer + endpoints: + default: 10 + type: integer + persistentVolumeClaims: + default: 10 + type: integer + pods: + default: 20 + type: integer + secrets: + default: 20 + type: integer + services: + default: 10 + type: integer + type: object + enabled: + default: true + type: boolean + limits: + properties: + cpu: + default: "0.5" + type: string + ephemeralStorage: + default: 5Gi + type: string + memory: + default: 8Gi + type: string + type: object + requests: + properties: + cpu: + default: "0.5" + type: string + ephemeralStorage: + default: 5Gi + type: string + memory: + default: 1Gi + type: string + storage: + default: 5Gi + type: string + type: object + services: + properties: + loadBalancers: + default: 3 + type: integer + nodePorts: + default: 0 + type: integer + type: object + required: + - enabled + type: object + sleep: + type: boolean + ttl: + type: string + type: object + status: + description: UffizziClusterStatus defines the observed state of UffizziCluster + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + helmReleaseRef: + type: string + host: + type: string + kubeConfig: + description: VClusterKubeConfig is the KubeConfig SecretReference + of the related VCluster + properties: + secretRef: + description: SecretKeyReference contains enough information to + locate the referenced Kubernetes Secret object in the same namespace. + Optionally a key can be specified. Use this type instead of + core/v1 SecretKeySelector when the Key is optional and the Optional + field is not applicable. + properties: + key: + description: Key in the Secret, when not specified an implementation-specific + default key is used. + type: string + name: + description: Name of the Secret. + type: string + required: + - name + type: object + type: object + lastAppliedConfiguration: + type: string + lastAppliedHelmReleaseSpec: + type: string + lastAwakeTime: + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml new file mode 100644 index 00000000..dde522ed --- /dev/null +++ b/config/rbac/role.yaml @@ -0,0 +1,169 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: manager-role +rules: +- apiGroups: + - "" + resources: + - pods + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - apps + resources: + - statefulsets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - helm.toolkit.fluxcd.io + resources: + - helmreleases + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - helm.toolkit.fluxcd.io + resources: + - helmreleases/finalizers + verbs: + - update +- apiGroups: + - helm.toolkit.fluxcd.io + resources: + - helmreleases/status + verbs: + - get + - patch + - update +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - networking.k8s.io + resources: + - networkpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - source.toolkit.fluxcd.io + resources: + - helmrepositories + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - source.toolkit.fluxcd.io + resources: + - helmrepositories/finalizers + verbs: + - update +- apiGroups: + - source.toolkit.fluxcd.io + resources: + - helmrepositories/status + verbs: + - get + - patch + - update +- apiGroups: + - uffizzi.com + resources: + - uffizziclusters + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - uffizzi.com + resources: + - uffizziclusters/finalizers + verbs: + - update +- apiGroups: + - uffizzi.com + resources: + - uffizziclusters/status + verbs: + - get + - patch + - update diff --git a/src/config/crd/bases/ucluster.uffizzi.com.uffizzi.com_uffizziclusters.yaml b/src/config/crd/bases/ucluster.uffizzi.com.uffizzi.com_uffizziclusters.yaml index 96bbf2e8..ef3a91c8 100644 --- a/src/config/crd/bases/ucluster.uffizzi.com.uffizzi.com_uffizziclusters.yaml +++ b/src/config/crd/bases/ucluster.uffizzi.com.uffizzi.com_uffizziclusters.yaml @@ -5,7 +5,7 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.11.1 creationTimestamp: null - name: UffizziClusters.uffizzi.com + name: uffizziclusters.uffizzi.com spec: group: uffizzi.com names: diff --git a/src/config/crd/patches/cainjection_in_ephemeralclusters.yaml b/src/config/crd/patches/cainjection_in_ephemeralclusters.yaml index 3ad271bd..414b7ebb 100644 --- a/src/config/crd/patches/cainjection_in_ephemeralclusters.yaml +++ b/src/config/crd/patches/cainjection_in_ephemeralclusters.yaml @@ -4,4 +4,4 @@ kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) - name: UffizziClusters.uffizzi.com + name: uffizziclusters.uffizzi.com diff --git a/src/controllers/uffizzicluster/suite_test.go b/src/controllers/uffizzicluster/suite_test.go index 41fce8dd..7bff181b 100644 --- a/src/controllers/uffizzicluster/suite_test.go +++ b/src/controllers/uffizzicluster/suite_test.go @@ -17,8 +17,11 @@ limitations under the License. package uffizzicluster import ( + "context" uclusteruffizzicomv1alpha1 "github.com/UffizziCloud/uffizzi-cluster-operator/src/api/v1alpha1" + "os" "path/filepath" + ctrl "sigs.k8s.io/controller-runtime" "testing" . "github.com/onsi/ginkgo/v2" @@ -36,9 +39,14 @@ import ( // These test use Ginkgo (BDD-style Go testing framework). Refer to // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. -var cfg *rest.Config -var k8sClient client.Client -var testEnv *envtest.Environment +var ( + cfg *rest.Config + k8sClient client.Client + testEnv *envtest.Environment + ctx context.Context + cancel context.CancelFunc + useExistingCluster = getEnvtestRemote() +) func TestAPIs(t *testing.T) { RegisterFailHandler(Fail) @@ -46,13 +54,22 @@ func TestAPIs(t *testing.T) { RunSpecs(t, "Controller Suite") } +// write a function to get the value of the ENVTEST_REMOTE environment variable +func getEnvtestRemote() bool { + if os.Getenv("ENVTEST_REMOTE") == "true" { + return true + } + return false +} + var _ = BeforeSuite(func() { logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, ErrorIfCRDPathMissing: true, + UseExistingCluster: &useExistingCluster, } var err error @@ -70,9 +87,27 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) + ctx, cancel = context.WithCancel(context.TODO()) + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + }) + Expect(err).ToNot(HaveOccurred()) + + err = (&UffizziClusterReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + go func() { + defer GinkgoRecover() + err = k8sManager.Start(ctx) + Expect(err).ToNot(HaveOccurred(), "failed to run manager") + }() }) var _ = AfterSuite(func() { + cancel() By("tearing down the test environment") err := testEnv.Stop() Expect(err).NotTo(HaveOccurred()) diff --git a/src/controllers/uffizzicluster/uffizzicluster_controller_test.go b/src/controllers/uffizzicluster/uffizzicluster_controller_test.go new file mode 100644 index 00000000..f84eed7c --- /dev/null +++ b/src/controllers/uffizzicluster/uffizzicluster_controller_test.go @@ -0,0 +1,14 @@ +package uffizzicluster + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("UffizziCluster Controller", func() { + Context("When creating a UffizziCluster", func() { + It("Should create a UffizziCluster", func() { + Expect(true).To(BeTrue()) + }) + }) +}) diff --git a/src/test/e2e/basic-k3s/00-assert.yaml b/src/test/e2e/basic-k3s/00-assert.yaml deleted file mode 100644 index f31cf94a..00000000 --- a/src/test/e2e/basic-k3s/00-assert.yaml +++ /dev/null @@ -1,7 +0,0 @@ -kind: StatefulSet -apiVersion: apps/v1 -metadata: - name: uc-basic-k3s -spec: - ingress: - host: app.qa-gke.uffizzi.com \ No newline at end of file diff --git a/src/test/e2e/basic-k3s/00-install.yaml b/src/test/e2e/basic-k3s/00-install.yaml deleted file mode 100644 index 9611d025..00000000 --- a/src/test/e2e/basic-k3s/00-install.yaml +++ /dev/null @@ -1,7 +0,0 @@ -kind: UffizziCluster -apiVersion: uffizzi.com/v1alpha1 -metadata: - name: basic-k3s -spec: - ingress: - host: app.qa-gke.uffizzi.com