From 5c2974f9e97315f69180eae647f8d3463ba43d6f Mon Sep 17 00:00:00 2001 From: Jason van Brackel Date: Wed, 9 Jun 2021 16:08:26 -0400 Subject: [PATCH 01/15] feat: Adding GitCredentials type and tests. --- Makefile | 2 +- PROJECT | 9 + .../v1alpha1/gitlabcredentials_types.go | 66 ++++ .../v1alpha1/gitlabcredentials_types_test.go | 293 ++++++++++++++++++ apis/gitlab/v1alpha1/zz_generated.deepcopy.go | 91 ++++++ ...ab.valkyrie.dso.mil_gitlabcredentials.yaml | 83 +++++ config/crd/kustomization.yaml | 3 + .../cainjection_in_gitlabcredentials.yaml | 7 + .../patches/webhook_in_gitlabcredentials.yaml | 14 + .../rbac/gitlabcredentials_editor_role.yaml | 24 ++ .../rbac/gitlabcredentials_viewer_role.yaml | 20 ++ config/rbac/role.yaml | 26 ++ .../gitlab_v1alpha1_gitlabcredentials.yaml | 7 + .../gitlab/gitlabcredentials_controller.go | 57 ++++ .../gitlabcredentials_controller_test.go | 66 ++++ controllers/gitlab/group_controller.go | 15 +- controllers/gitlab/group_controller_test.go | 120 +++++++ controllers/gitlab/mocks_test.go | 276 +++++++++++++++++ controllers/gitlab/suite_test.go | 28 +- driver.go | 5 + driver_test.go | 65 ++-- go.mod | 1 + main.go | 5 +- main_test.go | 2 +- mocks_test.go | 142 ++++++--- 25 files changed, 1320 insertions(+), 107 deletions(-) create mode 100644 apis/gitlab/v1alpha1/gitlabcredentials_types.go create mode 100755 apis/gitlab/v1alpha1/gitlabcredentials_types_test.go create mode 100644 config/crd/bases/gitlab.valkyrie.dso.mil_gitlabcredentials.yaml create mode 100644 config/crd/patches/cainjection_in_gitlabcredentials.yaml create mode 100644 config/crd/patches/webhook_in_gitlabcredentials.yaml create mode 100644 config/rbac/gitlabcredentials_editor_role.yaml create mode 100644 config/rbac/gitlabcredentials_viewer_role.yaml create mode 100644 config/samples/gitlab_v1alpha1_gitlabcredentials.yaml create mode 100644 controllers/gitlab/gitlabcredentials_controller.go create mode 100755 controllers/gitlab/gitlabcredentials_controller_test.go create mode 100755 controllers/gitlab/group_controller_test.go create mode 100755 controllers/gitlab/mocks_test.go diff --git a/Makefile b/Makefile index 2971728..3d5967e 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ ENVTEST_ASSETS_DIR=$(shell pwd)/testbin test: manifests generate fmt vet ## Run tests. mkdir -p ${ENVTEST_ASSETS_DIR} test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.2/hack/setup-envtest.sh - source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out + source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out ; go tool cover -html=./cover.out -o ./coverage.html ##@ Build diff --git a/PROJECT b/PROJECT index f377d7f..e3cfad4 100644 --- a/PROJECT +++ b/PROJECT @@ -122,4 +122,13 @@ resources: kind: Project path: valkyrie.dso.mil/valkyrie-api/apis/sonarqube/v1 version: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: valkyrie.dso.mil + group: gitlab + kind: GitlabCredentials + path: valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1 + version: v1alpha1 version: "3" diff --git a/apis/gitlab/v1alpha1/gitlabcredentials_types.go b/apis/gitlab/v1alpha1/gitlabcredentials_types.go new file mode 100644 index 0000000..9414004 --- /dev/null +++ b/apis/gitlab/v1alpha1/gitlabcredentials_types.go @@ -0,0 +1,66 @@ +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GitlabCredentialsSpec defines the desired state of GitlabCredentials, this stores a Gitlab username +// and Access Token for communicating with the Gitlab API. +type GitlabCredentialsSpec struct { + // URL is the url for the GitLab API that will be contacted. + Url string `json:"url,omitempty"` + + // Username is the Gitlab username for the account that will be communicating with the Gitlab API + Username string `json:"username,omitempty"` + + //AccessToken is the SecretRef to the secret containing the Gitlab Access Token for the user. + AccessToken v1.SecretReference `json:"access_token,omitempty"` +} + +// GitlabCredentialsStatus defines the observed state of GitlabCredentials +type GitlabCredentialsStatus struct { + // LastUsedTime is the time that this credential was last used to access a GitLab API + LastUsedDate metav1.Time `json:"last_used_date"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// GitlabCredentials is the Schema for the gitlabcredentials API +type GitlabCredentials struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GitlabCredentialsSpec `json:"spec,omitempty"` + Status GitlabCredentialsStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// GitlabCredentialsList contains a list of GitlabCredentials +type GitlabCredentialsList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []GitlabCredentials `json:"items"` +} + +func init() { + SchemeBuilder.Register(&GitlabCredentials{}, &GitlabCredentialsList{}) +} diff --git a/apis/gitlab/v1alpha1/gitlabcredentials_types_test.go b/apis/gitlab/v1alpha1/gitlabcredentials_types_test.go new file mode 100755 index 0000000..447b7e8 --- /dev/null +++ b/apis/gitlab/v1alpha1/gitlabcredentials_types_test.go @@ -0,0 +1,293 @@ +package v1alpha1 + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + v1 "k8s.io/api/core/v1" + "reflect" + "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Reusable test variables +type testVarsGitlabcredentials = struct { + testKind string + testApiversion string + testSpec string + testStatus string + + expectedKind string + expectedApiversion string + expectedSpec string + expectedStatus string + + testObject1 GitlabCredentials + testObject2 GitlabCredentials + + objectItems1 []GitlabCredentials + objectList1 GitlabCredentialsList + + objectItems2 []GitlabCredentials + objectList2 GitlabCredentialsList + + testObjectSpec1 GitlabCredentialsSpec + testObjectSpec2 GitlabCredentialsSpec + + testObjectStatus1 GitlabCredentialsStatus + testObjectStatus2 GitlabCredentialsStatus +} + +func initVarsGitlabCredentials() testVarsGitlabcredentials { + testVars := testVarsGitlabcredentials{} + + testVars.testKind = "TestKind" + testVars.testApiversion = "v22" + testVars.testSpec = "test spec value" + testVars.testStatus = "test status value" + + testVars.expectedApiversion = testVars.testApiversion + testVars.expectedKind = testVars.testKind + testVars.expectedSpec = testVars.testSpec + testVars.expectedStatus = testVars.testStatus + + var object1Metatype metav1.TypeMeta = metav1.TypeMeta{Kind: testVars.testKind, APIVersion: testVars.testApiversion} + testVars.testObject1 = GitlabCredentials{TypeMeta: object1Metatype} + + var object2Metatype metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind2", APIVersion: "V99"} + testVars.testObject2 = GitlabCredentials{TypeMeta: object2Metatype} + + var objectList1Metatype metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind_List", APIVersion: "V12"} + var objectItems1 []GitlabCredentials = []GitlabCredentials{testVars.testObject1, testVars.testObject2} + testVars.objectList1 = GitlabCredentialsList{TypeMeta: objectList1Metatype, Items: objectItems1} + + var objectList2Metatype metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind_List", APIVersion: "V12"} + var objectItems2 []GitlabCredentials = []GitlabCredentials{testVars.testObject2} + testVars.objectList2 = GitlabCredentialsList{TypeMeta: objectList2Metatype, Items: objectItems2} + + testVars.testObjectSpec1 = GitlabCredentialsSpec{ + Url: "https://example1.com", + Username: "", + AccessToken: v1.SecretReference{ + Name: "mySecret", + Namespace: "aNamespace", + }} + testVars.testObjectSpec2 = GitlabCredentialsSpec{ + Url: "https://example.com", + Username: "user2", + AccessToken: v1.SecretReference{ + Name: "mySecret2", + Namespace: "aNamespace2", + }, + } + + // leave scaffold Foo value for testing? + testVars.testObjectStatus1 = GitlabCredentialsStatus{ + LastUsedDate: metav1.Time{ + Time: time.Now(), + }, + } + testVars.testObjectStatus2 = GitlabCredentialsStatus{ + LastUsedDate: metav1.Time{ + Time: time.Now(), + }, + } + + return testVars +} + +func TestGroupVars_GitlabCredentials(t *testing.T) { + + xType := reflect.TypeOf(GroupVersion) + // convert object type to string + got := xType.String() + want := "schema.GroupVersion" + + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +// Test Type called GitlabCredentials +func TestTypes_GitlabCredentials(t *testing.T) { + testVariables := initVarsGitlabCredentials() + want := testVariables.expectedApiversion + + got := testVariables.testObject1.APIVersion + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +// DeepCopy +func TestDeepCopy_DeepCopy_GitlabCredentials(t *testing.T) { + testVariables := initVarsGitlabCredentials() + + newObject := testVariables.testObject1.DeepCopy() + + // check api version + got := newObject.APIVersion + want := testVariables.expectedApiversion + + if got != want { + t.Errorf("got %s want %s", got, want) + } + + // check kind + got = newObject.Kind + want = testVariables.expectedKind + + if got != want { + t.Errorf("got %s want %s", got, want) + } + + var nilTestPtr *GitlabCredentials = nil + var val = nilTestPtr.DeepCopyObject() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + + t.Log("Success") +} + +func TestDeepCopy_DeepCopyInto_GitlabCredentials(t *testing.T) { + testVariables := initVarsGitlabCredentials() + + testVariables.testObject1.DeepCopyInto(&testVariables.testObject2) + + got := testVariables.testObject2.APIVersion + want := testVariables.expectedApiversion + + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +func TestDeepCopy_DeepCopyObject_GitlabCredentials(t *testing.T) { + testVariables := initVarsGitlabCredentials() + + newRuntimeObject := testVariables.testObject1.DeepCopyObject() + newObject := newRuntimeObject.(*GitlabCredentials) + got := newObject.APIVersion + want := testVariables.expectedApiversion + + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +func TestDeepCopy_DeepCopyList_GitlabCredentials(t *testing.T) { + testVariables := initVarsGitlabCredentials() + newObjectList := testVariables.objectList1.DeepCopy() + + got := newObjectList.Items[0].APIVersion + want := testVariables.expectedApiversion + + if got != want { + t.Errorf("got %s want %s", got, want) + } + + // a typed pointer set to nil + var nilTestPtr *GitlabCredentialsList = nil + var val = nilTestPtr.DeepCopy() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + t.Log("Success") +} + +func TestDeepCopy_DeepCopyIntoList_GitlabCredentials(t *testing.T) { + testVariables := initVarsGitlabCredentials() + + testVariables.objectList1.DeepCopyInto(&testVariables.objectList2) + + got := testVariables.objectList2.Items[0].APIVersion + want := testVariables.expectedApiversion + + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +func TestDeepCopy_DeepCopyListObject_GitlabCredentials(t *testing.T) { + testVariables := initVarsGitlabCredentials() + + newRuntimeObject := testVariables.objectList1.DeepCopyObject() + newObject := newRuntimeObject.(*GitlabCredentialsList) + got := newObject.Items[0].APIVersion + want := testVariables.expectedApiversion + + if got != want { + t.Errorf("got %s want %s", got, want) + } + + var nil_test_ptr *GitlabCredentialsList = nil + var val = nil_test_ptr.DeepCopyObject() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + + t.Log("Success") +} + +func TestDeepCopy_DeepCopySpec_GitlabCredentials(t *testing.T) { + testVariables := initVarsGitlabCredentials() + + newObjectList := testVariables.testObjectSpec1.DeepCopy() + + It("Should DeepCopy the spec", func() { + Expect(newObjectList).To(Equal(testVariables.testObjectSpec1)) + }) + + var nil_test_ptr *GitlabCredentialsSpec = nil + var val = nil_test_ptr.DeepCopy() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + t.Log("Success") +} + +func TestDeepCopy_DeepCopySpecInto_GitlabCredentials(t *testing.T) { + testVariables := initVarsGitlabCredentials() + + testVariables.testObjectSpec1.DeepCopyInto(&testVariables.testObjectSpec2) + + It("Should DeepCopInto the spec", func() { + Expect(testVariables.testObjectSpec2).To(Equal(testVariables.testObjectSpec1)) + }) +} + +func TestDeepCopy_DeepCopyStatus_GitlabCredentials(t *testing.T) { + testVariables := initVarsGitlabCredentials() + + newObjectStatus := testVariables.testObjectStatus1.DeepCopy() + + It("Should DeepCopy the Status", func() { + Expect(newObjectStatus).To(Equal(testVariables.testObjectStatus1)) + }) + + // a typed pointer set to nil + var nil_test_ptr *GitlabCredentialsStatus = nil + var val = nil_test_ptr.DeepCopy() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + + t.Log("Success") +} + +func TestDeepCopy_DeepCopyStatusInto_GitlabCredentials(t *testing.T) { + testVariables := initVarsGitlabCredentials() + + testVariables.testObjectStatus1.DeepCopyInto(&testVariables.testObjectStatus2) + + It("Should DeepCopyInto the status.", func() { + Expect(testVariables.testObjectStatus2).To(Equal(testVariables.testObjectStatus1)) + }) +} diff --git a/apis/gitlab/v1alpha1/zz_generated.deepcopy.go b/apis/gitlab/v1alpha1/zz_generated.deepcopy.go index 8265bc6..9631bbc 100644 --- a/apis/gitlab/v1alpha1/zz_generated.deepcopy.go +++ b/apis/gitlab/v1alpha1/zz_generated.deepcopy.go @@ -113,6 +113,97 @@ func (in *FortifyPipelineConfigurationStatus) DeepCopy() *FortifyPipelineConfigu return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitlabCredentials) DeepCopyInto(out *GitlabCredentials) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitlabCredentials. +func (in *GitlabCredentials) DeepCopy() *GitlabCredentials { + if in == nil { + return nil + } + out := new(GitlabCredentials) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GitlabCredentials) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitlabCredentialsList) DeepCopyInto(out *GitlabCredentialsList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]GitlabCredentials, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitlabCredentialsList. +func (in *GitlabCredentialsList) DeepCopy() *GitlabCredentialsList { + if in == nil { + return nil + } + out := new(GitlabCredentialsList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GitlabCredentialsList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitlabCredentialsSpec) DeepCopyInto(out *GitlabCredentialsSpec) { + *out = *in + out.AccessToken = in.AccessToken +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitlabCredentialsSpec. +func (in *GitlabCredentialsSpec) DeepCopy() *GitlabCredentialsSpec { + if in == nil { + return nil + } + out := new(GitlabCredentialsSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitlabCredentialsStatus) DeepCopyInto(out *GitlabCredentialsStatus) { + *out = *in + in.LastUsedDate.DeepCopyInto(&out.LastUsedDate) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitlabCredentialsStatus. +func (in *GitlabCredentialsStatus) DeepCopy() *GitlabCredentialsStatus { + if in == nil { + return nil + } + out := new(GitlabCredentialsStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Group) DeepCopyInto(out *Group) { *out = *in diff --git a/config/crd/bases/gitlab.valkyrie.dso.mil_gitlabcredentials.yaml b/config/crd/bases/gitlab.valkyrie.dso.mil_gitlabcredentials.yaml new file mode 100644 index 0000000..1c6de61 --- /dev/null +++ b/config/crd/bases/gitlab.valkyrie.dso.mil_gitlabcredentials.yaml @@ -0,0 +1,83 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: gitlabcredentials.gitlab.valkyrie.dso.mil +spec: + group: gitlab.valkyrie.dso.mil + names: + kind: GitlabCredentials + listKind: GitlabCredentialsList + plural: gitlabcredentials + singular: gitlabcredentials + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: GitlabCredentials is the Schema for the gitlabcredentials 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: GitlabCredentialsSpec defines the desired state of GitlabCredentials, + this stores a Gitlab username and Access Token for communicating with + the Gitlab API. + properties: + access_token: + description: AccessToken is the SecretRef to the secret containing + the Gitlab Access Token for the user. + 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 + url: + description: URL is the url for the GitLab API that will be contacted. + type: string + username: + description: Username is the Gitlab username for the account that + will be communicating with the Gitlab API + type: string + type: object + status: + description: GitlabCredentialsStatus defines the observed state of GitlabCredentials + properties: + last_used_date: + description: LastUsedTime is the time that this credential was last + used to access a GitLab API + format: date-time + type: string + required: + - last_used_date + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index e4cfccc..b7fc193 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -15,6 +15,7 @@ resources: - bases/customer.valkyrie.dso.mil_systemowners.yaml - bases/customer.valkyrie.dso.mil_chiefinformationsecurityofficers.yaml - bases/sonarqube.valkyrie.dso.mil_projects.yaml +- bases/gitlab.valkyrie.dso.mil_gitlabcredentials.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -32,6 +33,7 @@ patchesStrategicMerge: #- patches/webhook_in_authorizingofficials.yaml #- patches/webhook_in_systemowners.yaml #- patches/webhook_in_chiefinformationsecurityofficers.yaml +#- patches/webhook_in_gitlabcredentials.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. @@ -48,6 +50,7 @@ patchesStrategicMerge: #- patches/cainjection_in_authorizingofficials.yaml #- patches/cainjection_in_systemowners.yaml #- patches/cainjection_in_chiefinformationsecurityofficers.yaml +#- patches/cainjection_in_gitlabcredentials.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_gitlabcredentials.yaml b/config/crd/patches/cainjection_in_gitlabcredentials.yaml new file mode 100644 index 0000000..7562b35 --- /dev/null +++ b/config/crd/patches/cainjection_in_gitlabcredentials.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: gitlabcredentials.gitlab.valkyrie.dso.mil diff --git a/config/crd/patches/webhook_in_gitlabcredentials.yaml b/config/crd/patches/webhook_in_gitlabcredentials.yaml new file mode 100644 index 0000000..f0b5aae --- /dev/null +++ b/config/crd/patches/webhook_in_gitlabcredentials.yaml @@ -0,0 +1,14 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: gitlabcredentials.gitlab.valkyrie.dso.mil +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert diff --git a/config/rbac/gitlabcredentials_editor_role.yaml b/config/rbac/gitlabcredentials_editor_role.yaml new file mode 100644 index 0000000..1c872c1 --- /dev/null +++ b/config/rbac/gitlabcredentials_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit gitlabcredentials. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: gitlabcredentials-editor-role +rules: +- apiGroups: + - gitlab.valkyrie.dso.mil + resources: + - gitlabcredentials + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - gitlab.valkyrie.dso.mil + resources: + - gitlabcredentials/status + verbs: + - get diff --git a/config/rbac/gitlabcredentials_viewer_role.yaml b/config/rbac/gitlabcredentials_viewer_role.yaml new file mode 100644 index 0000000..fdb5d03 --- /dev/null +++ b/config/rbac/gitlabcredentials_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view gitlabcredentials. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: gitlabcredentials-viewer-role +rules: +- apiGroups: + - gitlab.valkyrie.dso.mil + resources: + - gitlabcredentials + verbs: + - get + - list + - watch +- apiGroups: + - gitlab.valkyrie.dso.mil + resources: + - gitlabcredentials/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 3bd0088..a7301ed 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -162,6 +162,32 @@ rules: - get - patch - update +- apiGroups: + - gitlab.valkyrie.dso.mil + resources: + - gitlabcredentials + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - gitlab.valkyrie.dso.mil + resources: + - gitlabcredentials/finalizers + verbs: + - update +- apiGroups: + - gitlab.valkyrie.dso.mil + resources: + - gitlabcredentials/status + verbs: + - get + - patch + - update - apiGroups: - gitlab.valkyrie.dso.mil resources: diff --git a/config/samples/gitlab_v1alpha1_gitlabcredentials.yaml b/config/samples/gitlab_v1alpha1_gitlabcredentials.yaml new file mode 100644 index 0000000..5aa1eef --- /dev/null +++ b/config/samples/gitlab_v1alpha1_gitlabcredentials.yaml @@ -0,0 +1,7 @@ +apiVersion: gitlab.valkyrie.dso.mil/v1alpha1 +kind: GitlabCredentials +metadata: + name: gitlabcredentials-sample +spec: + # Add fields here + foo: bar diff --git a/controllers/gitlab/gitlabcredentials_controller.go b/controllers/gitlab/gitlabcredentials_controller.go new file mode 100644 index 0000000..3ec1241 --- /dev/null +++ b/controllers/gitlab/gitlabcredentials_controller.go @@ -0,0 +1,57 @@ +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gitlab + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" +) + +// GitlabCredentialsReconciler reconciles a GitlabCredentials object +type GitlabCredentialsReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=gitlabcredentials,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=gitlabcredentials/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=gitlabcredentials/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. We're leaving this empty for now as a +// placeholder for future need. +func (r *GitlabCredentialsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = r.Log.WithValues("gitlabcredentials", req.NamespacedName) + + // No logic here by intention. This is a placeholder for future logic. + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *GitlabCredentialsReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&gitlabv1alpha1.GitlabCredentials{}). + Complete(r) +} diff --git a/controllers/gitlab/gitlabcredentials_controller_test.go b/controllers/gitlab/gitlabcredentials_controller_test.go new file mode 100755 index 0000000..691897c --- /dev/null +++ b/controllers/gitlab/gitlabcredentials_controller_test.go @@ -0,0 +1,66 @@ +package gitlab + +import ( + "context" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "valkyrie.dso.mil/valkyrie-api/apis/customer/v1alpha1" + gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" +) + +var _ = Describe("gitlabcredentials_controller", func() { + Describe("Reconcile", func() { + logger := MockLogger{ + WithValuesKeysAndValues: make([]interface{}, 0), + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + sut := GitlabCredentialsReconciler{ + Client: nil, + Log: &logger, + Scheme: nil, + } + mockRequest := ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "mockNamespace", + Name: "mockName", + }, + } + + var result, err = sut.Reconcile(context.TODO(), mockRequest) + It("Should setup the Log", func() { + Expect(logger.WithValuesKeysAndValues).To(ContainElement("gitlabcredentials")) + Expect(logger.WithValuesKeysAndValues).To(ContainElement(mockRequest.NamespacedName)) + }) + + It("Should return a results and no error", func() { + Expect(result).To(BeEquivalentTo(ctrl.Result{})) + Expect(err).To(BeNil()) + }) + }) + Describe("SetupWithManager", func() { + v1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.GitlabCredentials{}, &gitlabv1alpha1.GitlabCredentialsList{}) + sut := GitlabCredentialsReconciler{ + Client: nil, + Log: nil, + Scheme: nil, + } + mgr := MockManager{ + builder: v1alpha1.SchemeBuilder, + Log: &MockLogger{}, + addHealthzCheckFunction: nil, + addHealthzCheckWasCalled: false, + addReadyzCheckFunction: nil, + addReadyzCheckWasCalled: false, + startFunction: nil, + startWasCalled: false, + } + + It("Should setup the GitLab Controller to be managed by the manager", func() { + Expect(sut.SetupWithManager(&mgr)).To(BeNil()) + }) + }) +}) diff --git a/controllers/gitlab/group_controller.go b/controllers/gitlab/group_controller.go index ce0be2a..3abf448 100644 --- a/controllers/gitlab/group_controller.go +++ b/controllers/gitlab/group_controller.go @@ -27,7 +27,8 @@ import ( gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" ) -// GroupReconciler reconciles a Group object +// GroupReconciler reconciles the state of a Gitlab Group, for each reconciliation loop it will log in to Gitlab +// with credentials in the secret provided. It will then check the state of the Group, and create it if necessary. type GroupReconciler struct { client.Client Log logr.Logger @@ -40,17 +41,13 @@ type GroupReconciler struct { // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the Group object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.2/pkg/reconcile +// The reconcile loop for the Gitlab Group will update the Group and any of the child Project API Objects. func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = r.Log.WithValues("group", req.NamespacedName) - // your logic here + // Get the Group Object + + // See that the secret exists return ctrl.Result{}, nil } diff --git a/controllers/gitlab/group_controller_test.go b/controllers/gitlab/group_controller_test.go new file mode 100755 index 0000000..c56e080 --- /dev/null +++ b/controllers/gitlab/group_controller_test.go @@ -0,0 +1,120 @@ +package gitlab + +import ( + . "github.com/onsi/ginkgo" +) + +var _ = Describe("group_controller", func() { + Describe("GroupReconciler", func() { + Describe("Reconcile", func() { + When("Getting ready to login", func() { + Context("Secret doesn't exist", func() { + It("Should change the status to MissingCredentials", func() { + //TODO + }) + It("Should log an error regarding the missing credentials", func() { + // TODO + }) + It("Should return an error", func() { + //TODO + }) + }) + It("Should log that it's going to log in with credentials (account token masked)", func() { + //TODO + }) + }) + When("Logging in to GitLab", func() { + It("Should log the attempt to login", func() { + // TODO + }) + Context("Unsuccessful login", func() { + It("Should log the login failure", func() { + // TODO + }) + It("Should return an error", func() { + // TODO + }) + }) + Context("Server Error", func() { + It("Should log the error", func() { + // TODO + }) + It("Should return the error", func() { + // TODO + }) + }) + It("Should log the successful login", func() { + // TODO + }) + }) + When("Reconciling the state of the Group", func() { + It("Should log that it's grabbing the group from the Kubernetes API", func() { + // TODO + }) + It("Should get the Group Object from the Kubernetes API", func() { + // TODO + }) + Context("Fails to get the group", func() { + It("Should log the error.", func() { + // TODO + }) + It("Should return an error", func() { + // TODO + }) + }) + It("Should log that it's grabbing the group from the Gitlab API", func() { + // TODO + }) + Context("Account has insufficient permissions", func() { + It("Should log the error", func() { + // TODO + }) + It("Should return an error", func() { + // TODO + }) + }) + Context("Server Error", func() { + It("Should log the error", func() { + // TODO + }) + It("Should return the error", func() { + // TODO + }) + }) + It("Should log that it's updating the Gitlab Group", func() { + // TODO + }) + It("Should update Gitlab Group using the Gitlab API", func() { + // TODO + }) + Context("Account has insufficient permissions", func() { + It("Should log the error", func() { + // TODO + }) + It("Should return an error", func() { + // TODO + }) + }) + Context("Server Error", func() { + It("Should log the error", func() { + // TODO + }) + It("Should return the error", func() { + // TODO + }) + }) + It("Should update the Group Object in the Kubernetes API", func() { + // TODO + }) + It("Should log successful update", func() { + // TODO + }) + It("Should return success", func() { + // TODO + }) + + // TODO, not sure what to do about Projects under the group yet, which is why I'm noting that here. + }) + }) + }) +}) diff --git a/controllers/gitlab/mocks_test.go b/controllers/gitlab/mocks_test.go new file mode 100755 index 0000000..f8f839d --- /dev/null +++ b/controllers/gitlab/mocks_test.go @@ -0,0 +1,276 @@ +package gitlab + +import ( + "context" + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + "net/http" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/scheme" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "time" + "valkyrie.dso.mil/valkyrie-api/controllers" +) + +type MockContext struct { +} + +func (m *MockContext) Deadline() (deadline time.Time, ok bool) { + panic("implement me") +} + +func (m *MockContext) Done() <-chan struct{} { + panic("implement me") +} + +func (m *MockContext) Err() error { + panic("implement me") +} + +func (m *MockContext) Value(key interface{}) interface{} { + panic("implement me") +} + +type MockLogger struct { + WithValuesKeysAndValues []interface{} + WithValuesCalled bool + WithNameValue string + WithNameCalled bool +} + +func (m *MockLogger) Enabled() bool { + panic("implement me") +} + +func (m *MockLogger) Info(msg string, keysAndValues ...interface{}) { + panic("implement me") +} + +func (m *MockLogger) Error(err error, msg string, keysAndValues ...interface{}) { + panic("implement me") +} + +func (m *MockLogger) V(level int) logr.Logger { + panic("implement me") +} + +func (m *MockLogger) WithValues(keysAndValues ...interface{}) logr.Logger { + m.WithValuesCalled = true + for i := 0; i < len(keysAndValues); i++ { + m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues[i]) + } + m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues) + + return m +} + +func (m *MockLogger) WithName(name string) logr.Logger { + m.WithNameCalled = true + m.WithNameValue = name + return m +} + +type MockController struct { + setupWithManagerWasCalled bool +} + +func (m *MockController) SetupWithManager(manager manager.Manager) error { + m.setupWithManagerWasCalled = true + return nil +} + +type MockManager struct { + Log logr.Logger + addHealthzCheckFunction func(name string, check healthz.Checker) error + addHealthzCheckWasCalled bool + addReadyzCheckFunction func(name string, check healthz.Checker) error + addReadyzCheckWasCalled bool + startFunction func(ctx context.Context) error + startWasCalled bool + builder *scheme.Builder + Fields []interface{} + runnable manager.Runnable +} + +func (m *MockManager) Add(runnable manager.Runnable) error { + m.runnable = runnable + + return nil +} + +func (m *MockManager) Elected() <-chan struct{} { + panic("implement me") +} + +func (m *MockManager) SetFields(i interface{}) error { + if m.Fields == nil { + m.Fields = make([]interface{}, 0) + } + m.Fields = append(m.Fields, i) + + return nil +} + +func (m *MockManager) AddMetricsExtraHandler(path string, handler http.Handler) error { + panic("implement me") +} + +func (m *MockManager) AddHealthzCheck(name string, check healthz.Checker) error { + m.addHealthzCheckWasCalled = true + if m.addHealthzCheckFunction == nil { + return nil + } + return m.addHealthzCheckFunction(name, check) +} + +func (m *MockManager) AddReadyzCheck(name string, check healthz.Checker) error { + m.addReadyzCheckWasCalled = true + if m.addReadyzCheckFunction == nil { + return nil + } + + return m.addReadyzCheckFunction(name, check) +} + +func (m *MockManager) Start(ctx context.Context) error { + m.startWasCalled = true + if m.startFunction == nil { + return nil + } + return m.startFunction(ctx) +} + +func (m *MockManager) GetConfig() *rest.Config { + return &rest.Config{} +} + +func (m *MockManager) GetScheme() *runtime.Scheme { + scheme, _ := m.builder.Build() + return scheme +} + +func (m *MockManager) GetClient() client.Client { + return nil +} + +func (m *MockManager) GetFieldIndexer() client.FieldIndexer { + panic("implement me") +} + +func (m *MockManager) GetCache() cache.Cache { + panic("implement me") +} + +func (m *MockManager) GetEventRecorderFor(name string) record.EventRecorder { + panic("implement me") +} + +func (m *MockManager) GetRESTMapper() meta.RESTMapper { + panic("implement me") +} + +func (m *MockManager) GetAPIReader() client.Reader { + panic("implement me") +} + +func (m *MockManager) GetWebhookServer() *webhook.Server { + panic("implement me") +} + +func (m *MockManager) GetLogger() logr.Logger { + return m.Log +} + +type MockRunFunctionParameters struct { + options zap.Options + metricsAddress string + healthProbeAddress string + enableLeaderElection bool +} + +type MockDriver struct { + parseCommandLineCalled bool + parseCommandLineFunction func() (string, bool, string, zap.Options, error) + runCalled bool + runFunction func(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) + runFunctionParameters MockRunFunctionParameters + newManagerCalled bool + newManagerFunction func(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) + instantiateControllersCalled bool + instantiateControllersFunction func(mgr manager.Manager) []controllers.ManagedController + setupControllerWithManagerCalled bool + setupControllersFunction func(controller controllers.ManagedController, manager manager.Manager) error +} + +func (m *MockDriver) parseCommandLine() (string, bool, string, zap.Options, error) { + m.parseCommandLineCalled = true + if m.parseCommandLineFunction == nil { + return "", true, "", zap.Options{}, nil + } + + return m.parseCommandLineFunction() +} + +func (m *MockDriver) run(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) { + m.runCalled = true + m.runFunctionParameters = MockRunFunctionParameters{ + options: opts, + metricsAddress: metricsAddress, + healthProbeAddress: healthProbeAddress, + enableLeaderElection: enableLeaderElection, + } + if m.runFunction == nil { + return 0, nil + } + + return m.runFunction(opts, metricsAddress, healthProbeAddress, enableLeaderElection) +} + +func (m *MockDriver) newManager(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) { + m.newManagerCalled = true + if m.newManagerFunction == nil { + return &MockManager{}, nil + } + + return m.newManagerFunction(metricsAddress, healthProbeAddress, enableLeaderElection) +} + +func (m *MockDriver) instantiateControllers(mgr manager.Manager) []controllers.ManagedController { + m.instantiateControllersCalled = true + + if m.instantiateControllersFunction == nil { + return []controllers.ManagedController{ + &MockController{}, + } + } + + return m.instantiateControllersFunction(mgr) +} + +func (m *MockDriver) setupControllerWithManager(controller controllers.ManagedController, manager manager.Manager) error { + m.setupControllerWithManagerCalled = true + + if m.setupControllersFunction == nil { + return nil + } + + return m.setupControllersFunction(controller, manager) +} + +type MockError struct { + message string +} + +func (m *MockError) Error() string { + if m.message == "" { + m.message = "mock Error" + } + return m.message +} diff --git a/controllers/gitlab/suite_test.go b/controllers/gitlab/suite_test.go index fa73a05..0bd5660 100644 --- a/controllers/gitlab/suite_test.go +++ b/controllers/gitlab/suite_test.go @@ -18,9 +18,10 @@ package gitlab import ( "path/filepath" - ctrl "sigs.k8s.io/controller-runtime" "testing" + ctrl "sigs.k8s.io/controller-runtime" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "k8s.io/client-go/kubernetes/scheme" @@ -66,24 +67,6 @@ var _ = BeforeSuite(func() { err = gitlabv1alpha1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) - //err = gitlabv1alpha1.AddToScheme(scheme.Scheme) - //Expect(err).NotTo(HaveOccurred()) - // - //err = gitlabv1alpha1.AddToScheme(scheme.Scheme) - //Expect(err).NotTo(HaveOccurred()) - // - //err = gitlabv1alpha1.AddToScheme(scheme.Scheme) - //Expect(err).NotTo(HaveOccurred()) - // - //err = gitlabv1alpha1.AddToScheme(scheme.Scheme) - //Expect(err).NotTo(HaveOccurred()) - // - //err = gitlabv1alpha1.AddToScheme(scheme.Scheme) - //Expect(err).NotTo(HaveOccurred()) - // - //err = gitlabv1alpha1.AddToScheme(scheme.Scheme) - //Expect(err).NotTo(HaveOccurred()) - //+kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) @@ -95,13 +78,6 @@ var _ = BeforeSuite(func() { }) Expect(err).ToNot(HaveOccurred()) - err = (&TwistlockPipelineConfigurationReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("gitlab").WithName("TwistlockPipelineConfiguration"), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr) - Expect(err).ToNot(HaveOccurred()) - go func() { err = mgr.Start(ctrl.SetupSignalHandler()) Expect(err).ToNot(HaveOccurred()) diff --git a/driver.go b/driver.go index 25f503d..29d95a4 100755 --- a/driver.go +++ b/driver.go @@ -177,6 +177,11 @@ func (d driverImpl) instantiateControllers(mgr manager.Manager) []controllers.Ma Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }, + &gitlabcontrollers.GitlabCredentialsReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("gitlab").WithName("GitlabCredentials"), + Scheme: mgr.GetScheme(), + }, //+kubebuilder:scaffold:builder } } diff --git a/driver_test.go b/driver_test.go index 00f7d27..3704f4e 100755 --- a/driver_test.go +++ b/driver_test.go @@ -16,7 +16,7 @@ import ( var _ = Describe("instantiateControllers", func() { When("Instantiating Controllers", func() { program := &driverImpl{} - mgr := mockManager{} + mgr := MockManager{} controllers := program.instantiateControllers(&mgr) It("Should create a Customer Controller", func() { Expect(controllers[0]).To(BeAssignableToTypeOf(&customer.Reconciler{})) @@ -54,6 +54,9 @@ var _ = Describe("instantiateControllers", func() { It("Should create a ChiefInformationSecurityOfficer Controller", func() { Expect(controllers[11]).To(BeAssignableToTypeOf(&customer.ChiefInformationSecurityOfficerReconciler{})) }) + It("Should create a GitlabCredentials Controller", func() { + Expect(controllers[12]).To(BeAssignableToTypeOf(&gitlab.GitlabCredentialsReconciler{})) + }) }) }) @@ -108,14 +111,14 @@ var _ = Describe("run", func() { metricsAddress := ":8080" healthProbeAddress := ":8081" enableLeaderElection := false - mgr := &mockManager{} + mgr := &MockManager{} Context("Normal operation", func() { - mockDriver := &mockDriver{ + MockDriver := &MockDriver{ parseCommandLineCalled: false, parseCommandLineFunction: nil, runCalled: false, runFunction: nil, - runFunctionParameters: runFunctionParameters{}, + runFunctionParameters: MockRunFunctionParameters{}, newManagerCalled: false, newManagerFunction: func(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) { return mgr, nil @@ -126,21 +129,21 @@ var _ = Describe("run", func() { setupControllersFunction: nil, } sut := &driverImpl{ - parseCommandLineOverride: mockDriver.parseCommandLine, + parseCommandLineOverride: MockDriver.parseCommandLine, runOverride: nil, - newManagerOverride: mockDriver.newManager, - instantiateControllersOverride: mockDriver.instantiateControllers, - setupControllerWithManagerOverride: mockDriver.setupControllerWithManager, + newManagerOverride: MockDriver.newManager, + instantiateControllersOverride: MockDriver.instantiateControllers, + setupControllerWithManagerOverride: MockDriver.setupControllerWithManager, } sut.run(options, metricsAddress, healthProbeAddress, enableLeaderElection) It("Should create a new manager", func() { - Expect(mockDriver.newManagerCalled).To(BeTrue()) + Expect(MockDriver.newManagerCalled).To(BeTrue()) }) It("Should instantiate controllers", func() { - Expect(mockDriver.instantiateControllersCalled).To(BeTrue()) + Expect(MockDriver.instantiateControllersCalled).To(BeTrue()) }) It("Should setup controllers with the manager", func() { - Expect(mockDriver.setupControllerWithManagerCalled).To(BeTrue()) + Expect(MockDriver.setupControllerWithManagerCalled).To(BeTrue()) }) It("Should add health check to the manager", func() { Expect(mgr.addHealthzCheckWasCalled).To(BeTrue()) @@ -153,15 +156,15 @@ var _ = Describe("run", func() { }) }) When("newManager fails.", func() { - newManagerError := mockError{ + newManagerError := MockError{ message: "mock newManager failure", } - mockDriver := &mockDriver{ + mockDriver := &MockDriver{ parseCommandLineCalled: false, parseCommandLineFunction: nil, runCalled: false, runFunction: nil, - runFunctionParameters: runFunctionParameters{}, + runFunctionParameters: MockRunFunctionParameters{}, newManagerCalled: false, newManagerFunction: func(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) { return nil, &newManagerError @@ -183,15 +186,15 @@ var _ = Describe("run", func() { }) }) When("setupControllerWithManager fails.", func() { - setupControllersError := mockError{ + setupControllersError := MockError{ message: "setupControllers Error mock", } - mockDriver := &mockDriver{ + mockDriver := &MockDriver{ parseCommandLineCalled: false, parseCommandLineFunction: nil, runCalled: false, runFunction: nil, - runFunctionParameters: runFunctionParameters{}, + runFunctionParameters: MockRunFunctionParameters{}, newManagerCalled: false, newManagerFunction: nil, instantiateControllersCalled: false, @@ -219,15 +222,15 @@ var _ = Describe("run", func() { }) When("The manager.AddHealthzCheck fails.", func() { - addHealthzError := &mockError{ + addHealthzError := &MockError{ message: "addHealthzCheck Function mocked failure", } - mockDriver := &mockDriver{ + mockDriver := &MockDriver{ parseCommandLineCalled: false, parseCommandLineFunction: nil, runCalled: false, runFunction: nil, - runFunctionParameters: runFunctionParameters{}, + runFunctionParameters: MockRunFunctionParameters{}, newManagerCalled: false, newManagerFunction: nil, instantiateControllersCalled: false, @@ -243,7 +246,7 @@ var _ = Describe("run", func() { setupControllerWithManagerOverride: mockDriver.setupControllerWithManager, ignoreControllerRuntime: false, } - mgr := mockManager{ + mgr := MockManager{ addHealthzCheckWasCalled: false, addReadyzCheckWasCalled: false, startWasCalled: false, @@ -263,15 +266,15 @@ var _ = Describe("run", func() { }) }) When("The manager.AddReadyzCheck fails.", func() { - addReadyzCheckError := &mockError{ + addReadyzCheckError := &MockError{ message: "addReadyzCheck Function mocked failure", } - mockDriver := &mockDriver{ + mockDriver := &MockDriver{ parseCommandLineCalled: false, parseCommandLineFunction: nil, runCalled: false, runFunction: nil, - runFunctionParameters: runFunctionParameters{}, + runFunctionParameters: MockRunFunctionParameters{}, newManagerCalled: false, newManagerFunction: nil, instantiateControllersCalled: false, @@ -287,7 +290,7 @@ var _ = Describe("run", func() { setupControllerWithManagerOverride: mockDriver.setupControllerWithManager, ignoreControllerRuntime: false, } - mgr := mockManager{ + mgr := MockManager{ addHealthzCheckWasCalled: false, addReadyzCheckWasCalled: false, startWasCalled: false, @@ -307,15 +310,15 @@ var _ = Describe("run", func() { }) }) When("The manager.Start fails.", func() { - startFunctionError := &mockError{ + startFunctionError := &MockError{ message: "Start Function mocked failure", } - mockDriver := &mockDriver{ + mockDriver := &MockDriver{ parseCommandLineCalled: false, parseCommandLineFunction: nil, runCalled: false, runFunction: nil, - runFunctionParameters: runFunctionParameters{}, + runFunctionParameters: MockRunFunctionParameters{}, newManagerCalled: false, newManagerFunction: nil, instantiateControllersCalled: false, @@ -323,7 +326,7 @@ var _ = Describe("run", func() { setupControllerWithManagerCalled: false, setupControllersFunction: nil, } - mgr := mockManager{ + mgr := MockManager{ addHealthzCheckWasCalled: false, addReadyzCheckWasCalled: false, startWasCalled: false, @@ -382,10 +385,10 @@ var _ = Describe("run", func() { var _ = Describe("setupControllerWithManager", func() { When("Setting up a controller with a manger", func() { program = &driverImpl{} - controller := mockController{ + controller := MockController{ setupWithManagerWasCalled: false, } - manager := mockManager{} + manager := MockManager{} program.setupControllerWithManager(&controller, &manager) It("Should call the controllers SetupWithManagerFunction", func() { Expect(controller.setupWithManagerWasCalled).To(Equal(true)) diff --git a/go.mod b/go.mod index 3797ad6..63f9ca7 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/go-logr/logr v0.3.0 github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.13.0 + k8s.io/api v0.19.2 k8s.io/apimachinery v0.19.2 k8s.io/client-go v0.19.2 sigs.k8s.io/controller-runtime v0.7.2 diff --git a/main.go b/main.go index 401b39b..bfbf48a 100644 --- a/main.go +++ b/main.go @@ -17,14 +17,17 @@ limitations under the License. package main import ( + "os" + "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "os" + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" ctrl "sigs.k8s.io/controller-runtime" + customerv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/customer/v1alpha1" gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" sonarqubev1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/sonarqube/v1alpha1" diff --git a/main_test.go b/main_test.go index d52969f..a4015dc 100755 --- a/main_test.go +++ b/main_test.go @@ -23,7 +23,7 @@ var _ = Describe("main", func() { ZapOpts: nil, } parseCommandResult5 error = nil - driver = &mockDriver{ + driver = &MockDriver{ parseCommandLineCalled: false, parseCommandLineFunction: func() (string, bool, string, zap.Options, error) { return parseCommandResult1, parseCommandResult2, parseCommandResult3, parseCommandResult4, parseCommandResult5 diff --git a/mocks_test.go b/mocks_test.go index 6b7964c..38d4503 100755 --- a/mocks_test.go +++ b/mocks_test.go @@ -14,44 +14,110 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/webhook" + "time" "valkyrie.dso.mil/valkyrie-api/controllers" ) -type mockController struct { +type MockContext struct { +} + +func (m *MockContext) Deadline() (deadline time.Time, ok bool) { + panic("implement me") +} + +func (m *MockContext) Done() <-chan struct{} { + panic("implement me") +} + +func (m *MockContext) Err() error { + panic("implement me") +} + +func (m *MockContext) Value(key interface{}) interface{} { + panic("implement me") +} + +type MockLogger struct { + WithValuesKeysAndValues []interface{} + WithValuesCalled bool + WithNameValue string + WithNameCalled bool +} + +func (m MockLogger) Enabled() bool { + panic("implement me") +} + +func (m MockLogger) Info(msg string, keysAndValues ...interface{}) { + panic("implement me") +} + +func (m MockLogger) Error(err error, msg string, keysAndValues ...interface{}) { + panic("implement me") +} + +func (m MockLogger) V(level int) logr.Logger { + panic("implement me") +} + +func (m MockLogger) WithValues(keysAndValues ...interface{}) logr.Logger { + m.WithValuesCalled = true + m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues) + + return m +} + +func (m MockLogger) WithName(name string) logr.Logger { + m.WithNameCalled = true + m.WithNameValue = name + return m +} + +type MockController struct { setupWithManagerWasCalled bool } -func (m *mockController) SetupWithManager(manager manager.Manager) error { +func (m *MockController) SetupWithManager(manager manager.Manager) error { m.setupWithManagerWasCalled = true return nil } -type mockManager struct { +type MockManager struct { + Log logr.Logger addHealthzCheckFunction func(name string, check healthz.Checker) error addHealthzCheckWasCalled bool addReadyzCheckFunction func(name string, check healthz.Checker) error addReadyzCheckWasCalled bool startFunction func(ctx context.Context) error startWasCalled bool + Fields []interface{} + runnable manager.Runnable } -func (m *mockManager) Add(runnable manager.Runnable) error { - panic("implement me") +func (m *MockManager) Add(runnable manager.Runnable) error { + m.runnable = runnable + + return nil } -func (m *mockManager) Elected() <-chan struct{} { +func (m *MockManager) Elected() <-chan struct{} { panic("implement me") } -func (m *mockManager) SetFields(i interface{}) error { - panic("implement me") +func (m *MockManager) SetFields(i interface{}) error { + if m.Fields == nil { + m.Fields = make([]interface{}, 0) + } + m.Fields = append(m.Fields, i) + + return nil } -func (m *mockManager) AddMetricsExtraHandler(path string, handler http.Handler) error { +func (m *MockManager) AddMetricsExtraHandler(path string, handler http.Handler) error { panic("implement me") } -func (m *mockManager) AddHealthzCheck(name string, check healthz.Checker) error { +func (m *MockManager) AddHealthzCheck(name string, check healthz.Checker) error { m.addHealthzCheckWasCalled = true if m.addHealthzCheckFunction == nil { return nil @@ -59,7 +125,7 @@ func (m *mockManager) AddHealthzCheck(name string, check healthz.Checker) error return m.addHealthzCheckFunction(name, check) } -func (m *mockManager) AddReadyzCheck(name string, check healthz.Checker) error { +func (m *MockManager) AddReadyzCheck(name string, check healthz.Checker) error { m.addReadyzCheckWasCalled = true if m.addReadyzCheckFunction == nil { return nil @@ -68,7 +134,7 @@ func (m *mockManager) AddReadyzCheck(name string, check healthz.Checker) error { return m.addReadyzCheckFunction(name, check) } -func (m *mockManager) Start(ctx context.Context) error { +func (m *MockManager) Start(ctx context.Context) error { m.startWasCalled = true if m.startFunction == nil { return nil @@ -76,59 +142,59 @@ func (m *mockManager) Start(ctx context.Context) error { return m.startFunction(ctx) } -func (m *mockManager) GetConfig() *rest.Config { - panic("implement me") +func (m *MockManager) GetConfig() *rest.Config { + return &rest.Config{} } -func (m *mockManager) GetScheme() *runtime.Scheme { - return nil +func (m *MockManager) GetScheme() *runtime.Scheme { + return &runtime.Scheme{} } -func (m *mockManager) GetClient() client.Client { +func (m *MockManager) GetClient() client.Client { return nil } -func (m *mockManager) GetFieldIndexer() client.FieldIndexer { +func (m *MockManager) GetFieldIndexer() client.FieldIndexer { panic("implement me") } -func (m *mockManager) GetCache() cache.Cache { +func (m *MockManager) GetCache() cache.Cache { panic("implement me") } -func (m *mockManager) GetEventRecorderFor(name string) record.EventRecorder { +func (m *MockManager) GetEventRecorderFor(name string) record.EventRecorder { panic("implement me") } -func (m *mockManager) GetRESTMapper() meta.RESTMapper { +func (m *MockManager) GetRESTMapper() meta.RESTMapper { panic("implement me") } -func (m *mockManager) GetAPIReader() client.Reader { +func (m *MockManager) GetAPIReader() client.Reader { panic("implement me") } -func (m *mockManager) GetWebhookServer() *webhook.Server { +func (m *MockManager) GetWebhookServer() *webhook.Server { panic("implement me") } -func (m *mockManager) GetLogger() logr.Logger { - panic("implement me") +func (m *MockManager) GetLogger() logr.Logger { + return m.Log } -type runFunctionParameters struct { +type MockRunFunctionParameters struct { options zap.Options metricsAddress string healthProbeAddress string enableLeaderElection bool } -type mockDriver struct { +type MockDriver struct { parseCommandLineCalled bool parseCommandLineFunction func() (string, bool, string, zap.Options, error) runCalled bool runFunction func(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) - runFunctionParameters runFunctionParameters + runFunctionParameters MockRunFunctionParameters newManagerCalled bool newManagerFunction func(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) instantiateControllersCalled bool @@ -137,7 +203,7 @@ type mockDriver struct { setupControllersFunction func(controller controllers.ManagedController, manager manager.Manager) error } -func (m *mockDriver) parseCommandLine() (string, bool, string, zap.Options, error) { +func (m *MockDriver) parseCommandLine() (string, bool, string, zap.Options, error) { m.parseCommandLineCalled = true if m.parseCommandLineFunction == nil { return "", true, "", zap.Options{}, nil @@ -146,9 +212,9 @@ func (m *mockDriver) parseCommandLine() (string, bool, string, zap.Options, erro return m.parseCommandLineFunction() } -func (m *mockDriver) run(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) { +func (m *MockDriver) run(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) { m.runCalled = true - m.runFunctionParameters = runFunctionParameters{ + m.runFunctionParameters = MockRunFunctionParameters{ options: opts, metricsAddress: metricsAddress, healthProbeAddress: healthProbeAddress, @@ -161,28 +227,28 @@ func (m *mockDriver) run(opts zap.Options, metricsAddress string, healthProbeAdd return m.runFunction(opts, metricsAddress, healthProbeAddress, enableLeaderElection) } -func (m *mockDriver) newManager(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) { +func (m *MockDriver) newManager(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) { m.newManagerCalled = true if m.newManagerFunction == nil { - return &mockManager{}, nil + return &MockManager{}, nil } return m.newManagerFunction(metricsAddress, healthProbeAddress, enableLeaderElection) } -func (m *mockDriver) instantiateControllers(mgr manager.Manager) []controllers.ManagedController { +func (m *MockDriver) instantiateControllers(mgr manager.Manager) []controllers.ManagedController { m.instantiateControllersCalled = true if m.instantiateControllersFunction == nil { return []controllers.ManagedController{ - &mockController{}, + &MockController{}, } } return m.instantiateControllersFunction(mgr) } -func (m *mockDriver) setupControllerWithManager(controller controllers.ManagedController, manager manager.Manager) error { +func (m *MockDriver) setupControllerWithManager(controller controllers.ManagedController, manager manager.Manager) error { m.setupControllerWithManagerCalled = true if m.setupControllersFunction == nil { @@ -192,11 +258,11 @@ func (m *mockDriver) setupControllerWithManager(controller controllers.ManagedCo return m.setupControllersFunction(controller, manager) } -type mockError struct { +type MockError struct { message string } -func (m *mockError) Error() string { +func (m *MockError) Error() string { if m.message == "" { m.message = "mock Error" } -- GitLab From ff33308391480e0fab5c08ca91314382a3c79e21 Mon Sep 17 00:00:00 2001 From: Jason van Brackel Date: Wed, 9 Jun 2021 16:40:31 -0400 Subject: [PATCH 02/15] WIP Add the group type --- apis/gitlab/v1alpha1/group_scaffold_test.go | 287 ----------------- apis/gitlab/v1alpha1/group_types.go | 18 +- apis/gitlab/v1alpha1/group_types_test.go | 300 ++++++++++++++++++ apis/gitlab/v1alpha1/zz_generated.deepcopy.go | 17 +- .../customer.valkyrie.dso.mil_customers.yaml | 213 ++++--------- .../bases/gitlab.valkyrie.dso.mil_groups.yaml | 222 ++++--------- 6 files changed, 451 insertions(+), 606 deletions(-) delete mode 100644 apis/gitlab/v1alpha1/group_scaffold_test.go create mode 100644 apis/gitlab/v1alpha1/group_types_test.go diff --git a/apis/gitlab/v1alpha1/group_scaffold_test.go b/apis/gitlab/v1alpha1/group_scaffold_test.go deleted file mode 100644 index fd72e24..0000000 --- a/apis/gitlab/v1alpha1/group_scaffold_test.go +++ /dev/null @@ -1,287 +0,0 @@ -package v1alpha1 - -import ( - "reflect" - "testing" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// Reusable test variables -type test_vars_Group = struct { - test_kind string - test_apiversion string - test_spec string - test_status string - - expected_kind string - expected_apiversion string - expected_spec string - expected_status string - - test_object1 Group - test_object2 Group - - object_items1 []Group - object_list1 GroupList - - object_items2 []Group - object_list2 GroupList - - // leave scaffold Foo value for testing? - test_object_spec1 GroupSpec - test_object_spec2 GroupSpec - - // leave scaffold Foo value for testing? - test_object_status1 GroupStatus - test_object_status2 GroupStatus -} - -func init_Vars_Group() test_vars_Group { - test_vars := test_vars_Group{} - - test_vars.test_kind = "TestKind" - test_vars.test_apiversion = "v22" - test_vars.test_spec = "test spec value" - test_vars.test_status = "test status value" - - test_vars.expected_apiversion = test_vars.test_apiversion - test_vars.expected_kind = test_vars.test_kind - test_vars.expected_spec = test_vars.test_spec - test_vars.expected_status = test_vars.test_status - - var object1_metaType metav1.TypeMeta = metav1.TypeMeta{Kind: test_vars.test_kind, APIVersion: test_vars.test_apiversion} - test_vars.test_object1 = Group{TypeMeta: object1_metaType} - - var object2_metaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind2", APIVersion: "V99"} - test_vars.test_object2 = Group{TypeMeta: object2_metaType} - - var object_list1_metaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind_List", APIVersion: "V12"} - var object_items1 []Group = []Group{test_vars.test_object1, test_vars.test_object2} - // test_object_list = GroupList(object_list1_metaType,nil,object_items) - test_vars.object_list1 = GroupList{TypeMeta: object_list1_metaType, Items: object_items1} - - var object_list2_metaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind_List", APIVersion: "V12"} - var object_items2 []Group = []Group{test_vars.test_object2} - // test_object_list = GroupList(object_list1_metaType,nil,object_items) - test_vars.object_list2 = GroupList{TypeMeta: object_list2_metaType, Items: object_items2} - - // leave scaffold Foo value for testing? - test_vars.test_object_spec1 = GroupSpec{Dummy: test_vars.test_spec} - test_vars.test_object_spec2 = GroupSpec{Dummy: "other value"} - - // leave scaffold Foo value for testing? - test_vars.test_object_status1 = GroupStatus{Dummy: test_vars.test_status} - test_vars.test_object_status2 = GroupStatus{Dummy: "other value"} - - return test_vars -} - -func TestGroupVars_Group(t *testing.T) { - - xType := reflect.TypeOf(GroupVersion) - // convert object type to string - got := xType.String() - want := "schema.GroupVersion" - - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} - -// Test Type called Group -func TestTypes_Group(t *testing.T) { - l_test_vars := init_Vars_Group() - want := l_test_vars.expected_apiversion - - got := l_test_vars.test_object1.APIVersion - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} - -// DeepCopy -func TestDeepCopy_DeepCopy_Group(t *testing.T) { - l_test_vars := init_Vars_Group() - - new_object := l_test_vars.test_object1.DeepCopy() - - // check api version - got := new_object.APIVersion - want := l_test_vars.expected_apiversion - - if got != want { - t.Errorf("got %s want %s", got, want) - } - - // check kind - got = new_object.Kind - want = l_test_vars.expected_kind - - if got != want { - t.Errorf("got %s want %s", got, want) - } - - var nil_test_ptr *Group = nil - var val = nil_test_ptr.DeepCopyObject() - if val != nil { - t.Errorf("got %s want %s", "not nil", "nil") - } - - t.Log("Success") -} - -func TestDeepCopy_DeepCopyInto_Group(t *testing.T) { - l_test_vars := init_Vars_Group() - - l_test_vars.test_object1.DeepCopyInto(&l_test_vars.test_object2) - - got := l_test_vars.test_object2.APIVersion - want := l_test_vars.expected_apiversion - - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} - -func TestDeepCopy_DeepCopyObject_Group(t *testing.T) { - l_test_vars := init_Vars_Group() - - new_runtime_object := l_test_vars.test_object1.DeepCopyObject() - new_object := new_runtime_object.(*Group) - got := new_object.APIVersion - want := l_test_vars.expected_apiversion - - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} - -func TestDeepCopy_DeepCopyList_Group(t *testing.T) { - l_test_vars := init_Vars_Group() - new_object_list := l_test_vars.object_list1.DeepCopy() - - got := new_object_list.Items[0].APIVersion - want := l_test_vars.expected_apiversion - - if got != want { - t.Errorf("got %s want %s", got, want) - } - - // a typed pointer set to nil - var nil_test_ptr *GroupList = nil - var val = nil_test_ptr.DeepCopy() - if val != nil { - t.Errorf("got %s want %s", "not nil", "nil") - } - t.Log("Success") -} - -func TestDeepCopy_DeepCopyIntoList_Group(t *testing.T) { - l_test_vars := init_Vars_Group() - - l_test_vars.object_list1.DeepCopyInto(&l_test_vars.object_list2) - - got := l_test_vars.object_list2.Items[0].APIVersion - want := l_test_vars.expected_apiversion - - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} - -func TestDeepCopy_DeepCopyListObject_Group(t *testing.T) { - l_test_vars := init_Vars_Group() - - new_runtime_object := l_test_vars.object_list1.DeepCopyObject() - new_object := new_runtime_object.(*GroupList) - got := new_object.Items[0].APIVersion - want := l_test_vars.expected_apiversion - - if got != want { - t.Errorf("got %s want %s", got, want) - } - - var nil_test_ptr *GroupList = nil - var val = nil_test_ptr.DeepCopyObject() - if val != nil { - t.Errorf("got %s want %s", "not nil", "nil") - } - - t.Log("Success") -} - -func TestDeepCopy_DeepCopySpec_Group(t *testing.T) { - l_test_vars := init_Vars_Group() - - new_object_list := l_test_vars.test_object_spec1.DeepCopy() - - got := new_object_list.Dummy - want := l_test_vars.expected_spec - - if got != want { - t.Errorf("got %s want %s", got, want) - } - - var nil_test_ptr *GroupSpec = nil - var val = nil_test_ptr.DeepCopy() - if val != nil { - t.Errorf("got %s want %s", "not nil", "nil") - } - t.Log("Success") -} - -func TestDeepCopy_DeepCopySpecInto_Group(t *testing.T) { - l_test_vars := init_Vars_Group() - - l_test_vars.test_object_spec1.DeepCopyInto(&l_test_vars.test_object_spec2) - - got := l_test_vars.test_object_spec2.Dummy - want := l_test_vars.expected_spec - - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} - -func TestDeepCopy_DeepCopyStatus_Group(t *testing.T) { - l_test_vars := init_Vars_Group() - - new_object_status := l_test_vars.test_object_status1.DeepCopy() - - got := new_object_status.Dummy - want := l_test_vars.expected_status - - if got != want { - t.Errorf("got %s want %s", got, want) - } - - // a typed pointer set to nil - var nil_test_ptr *GroupStatus = nil - var val = nil_test_ptr.DeepCopy() - if val != nil { - t.Errorf("got %s want %s", "not nil", "nil") - } - - t.Log("Success") -} - -func TestDeepCopy_DeepCopyStatusInto_Group(t *testing.T) { - l_test_vars := init_Vars_Group() - - l_test_vars.test_object_status1.DeepCopyInto(&l_test_vars.test_object_status2) - - got := l_test_vars.test_object_status2.Dummy - want := l_test_vars.expected_status - - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} diff --git a/apis/gitlab/v1alpha1/group_types.go b/apis/gitlab/v1alpha1/group_types.go index b571369..d6d625d 100644 --- a/apis/gitlab/v1alpha1/group_types.go +++ b/apis/gitlab/v1alpha1/group_types.go @@ -26,20 +26,20 @@ type GroupSpec struct { // +kubebuilder:validation:required Name string `json:"name"` - // Projects are the GitLab Projects managed by this Group - Projects ProjectList `json:"projects:omitempty"` + // GitlabCredentialsName is the name of the object in this namespace that contains authentication + // information for logging into the + GitlabCredentialsName string `json:"gitlab_credentials_name"` - // test Dummy is an example field of Project. Edit project_types.go to remove/update - Dummy string `json:"dummy,omitempty"` + // ProjectSpecs are for the GitLab projects managed by this Group. It's expected that Projects + // will be managed at the group level rather than be adjusted themselves. + ProjectSpecs []ProjectSpec `json:"projects:omitempty"` } // GroupStatus defines the observed state of Group type GroupStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - - // test Dummy is an example field of Project. Edit project_types.go to remove/update - Dummy string `json:"dummy,omitempty"` + GitlabId *int64 `json:"gitlab_id"` + CreatedTime metav1.Time `json:"created_time"` + LastUpdatedTime metav1.Time `json:"last_updated_time"` } //+kubebuilder:object:root=true diff --git a/apis/gitlab/v1alpha1/group_types_test.go b/apis/gitlab/v1alpha1/group_types_test.go new file mode 100644 index 0000000..0945cb5 --- /dev/null +++ b/apis/gitlab/v1alpha1/group_types_test.go @@ -0,0 +1,300 @@ +package v1alpha1 + +import ( + "reflect" + "testing" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Reusable test variables +type testVarsGroup = struct { + testKind string + testApiVersion string + testSpec string + testStatus string + + expectedKind string + expectedApiVersion string + expectedSpec string + expectedStatus string + + testObject1 Group + testObject2 Group + + objectItems1 []Group + objectList1 GroupList + + objectItems2 []Group + objectList2 GroupList + + testObjectSpec1 GroupSpec + testObjectSpec2 GroupSpec + + testObjectStatus1 GroupStatus + testObjectStatus2 GroupStatus +} + +func initVarsGroup() testVarsGroup { + testVars := testVarsGroup{} + + testVars.testKind = "TestKind" + testVars.testApiVersion = "v22" + testVars.testSpec = "test spec value" + testVars.testStatus = "test status value" + + testVars.expectedApiVersion = testVars.testApiVersion + testVars.expectedKind = testVars.testKind + testVars.expectedSpec = testVars.testSpec + testVars.expectedStatus = testVars.testStatus + + var object1MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: testVars.testKind, APIVersion: testVars.testApiVersion} + testVars.testObject1 = Group{TypeMeta: object1MetaType} + + var object2MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind2", APIVersion: "V99"} + testVars.testObject2 = Group{TypeMeta: object2MetaType} + + var objectList1MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind_List", APIVersion: "V12"} + var objectItems1 []Group = []Group{testVars.testObject1, testVars.testObject2} + // test_object_list = GroupList(object_list1_metaType,nil,object_items) + testVars.objectList1 = GroupList{TypeMeta: objectList1MetaType, Items: objectItems1} + + var objectList2Metatype metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind_List", APIVersion: "V12"} + var objectItems2 []Group = []Group{testVars.testObject2} + // test_object_list = GroupList(object_list1_metaType,nil,object_items) + testVars.objectList2 = GroupList{TypeMeta: objectList2Metatype, Items: objectItems2} + + testVars.testObjectSpec1 = GroupSpec{ + Name: "testGroup1", + GitlabCredentialsName: "nameOfTheCredentials", + } + testVars.testObjectSpec2 = GroupSpec{ + Name: "testGroup2", + GitlabCredentialsName: "nameOfCredentials2", + ProjectSpecs: nil, + } + + id1 := int64(1) + testVars.testObjectStatus1 = GroupStatus{ + GitlabId: &id1, + CreatedTime: metav1.Time{ + Time: time.Now(), + }, + LastUpdatedTime: metav1.Time{ + Time: time.Now(), + }, + } + + id2 := int64(2) + testVars.testObjectStatus2 = GroupStatus{ + GitlabId: &id2, + CreatedTime: metav1.Time{ + Time: time.Now(), + }, + LastUpdatedTime: metav1.Time{ + Time: time.Now(), + }, + } + + return testVars +} + +func TestGroupVars_Group(t *testing.T) { + + xType := reflect.TypeOf(GroupVersion) + // convert object type to string + got := xType.String() + want := "schema.GroupVersion" + + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +// Test Type called Group +func TestTypes_Group(t *testing.T) { + lTestVars := initVarsGroup() + want := lTestVars.expectedApiVersion + + got := lTestVars.testObject1.APIVersion + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +// DeepCopy +func TestDeepCopy_DeepCopy_Group(t *testing.T) { + lTestVars := initVarsGroup() + + newObject := lTestVars.testObject1.DeepCopy() + + // check api version + got := newObject.APIVersion + want := lTestVars.expectedApiVersion + + if got != want { + t.Errorf("got %s want %s", got, want) + } + + // check kind + got = newObject.Kind + want = lTestVars.expectedKind + + if got != want { + t.Errorf("got %s want %s", got, want) + } + + var nilTestPtr *Group = nil + var val = nilTestPtr.DeepCopyObject() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + + t.Log("Success") +} + +func TestDeepCopy_DeepCopyInto_Group(t *testing.T) { + lTestVars := initVarsGroup() + + lTestVars.testObject1.DeepCopyInto(&lTestVars.testObject2) + + got := lTestVars.testObject2.APIVersion + want := lTestVars.expectedApiVersion + + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +func TestDeepCopy_DeepCopyObject_Group(t *testing.T) { + lTestVars := initVarsGroup() + + newRuntimeObject := lTestVars.testObject1.DeepCopyObject() + newObject := newRuntimeObject.(*Group) + got := newObject.APIVersion + want := lTestVars.expectedApiVersion + + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +func TestDeepCopy_DeepCopyList_Group(t *testing.T) { + lTestVars := initVarsGroup() + newObjectList := lTestVars.objectList1.DeepCopy() + + got := newObjectList.Items[0].APIVersion + want := lTestVars.expectedApiVersion + + if got != want { + t.Errorf("got %s want %s", got, want) + } + + // a typed pointer set to nil + var nilTestPtr *GroupList = nil + var val = nilTestPtr.DeepCopy() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + t.Log("Success") +} + +func TestDeepCopy_DeepCopyIntoList_Group(t *testing.T) { + lTestVars := initVarsGroup() + + lTestVars.objectList1.DeepCopyInto(&lTestVars.objectList2) + + got := lTestVars.objectList2.Items[0].APIVersion + want := lTestVars.expectedApiVersion + + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +func TestDeepCopy_DeepCopyListObject_Group(t *testing.T) { + lTestVars := initVarsGroup() + + newRuntimeObject := lTestVars.objectList1.DeepCopyObject() + newObject := newRuntimeObject.(*GroupList) + got := newObject.Items[0].APIVersion + want := lTestVars.expectedApiVersion + + if got != want { + t.Errorf("got %s want %s", got, want) + } + + var nilTestPtr *GroupList = nil + var val = nilTestPtr.DeepCopyObject() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + + t.Log("Success") +} + +func TestDeepCopy_DeepCopySpec_Group(t *testing.T) { + lTestVars := initVarsGroup() + + newObjectList := lTestVars.testObjectSpec1.DeepCopy() + + It("Should DeepCopy the Spec", func() { + Expect(newObjectList).To(Equal(lTestVars.testObjectSpec1)) + }) + + var nilTestPtr *GroupSpec = nil + var val = nilTestPtr.DeepCopy() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + t.Log("Success") +} + +func TestDeepCopy_DeepCopySpecInto_Group(t *testing.T) { + lTestVars := initVarsGroup() + + lTestVars.testObjectSpec1.DeepCopyInto(&lTestVars.testObjectSpec2) + + It("Should DeepCopyInto a Group", func() { + Expect(lTestVars.testObjectSpec1).To(Equal(lTestVars.testObject2)) + }) +} + +func TestDeepCopy_DeepCopyStatus_Group(t *testing.T) { + lTestVars := initVarsGroup() + + newObjectStatus := lTestVars.testObjectStatus1.DeepCopy() + + It("Should DeepCopy the Status", func() { + Expect(newObjectStatus).To(Equal(lTestVars.testObjectStatus1)) + }) + + // a typed pointer set to nil + var nilTestPtr *GroupStatus = nil + var val = nilTestPtr.DeepCopy() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + + t.Log("Success") +} + +func TestDeepCopy_DeepCopyStatusInto_Group(t *testing.T) { + lTestVars := initVarsGroup() + + lTestVars.testObjectStatus1.DeepCopyInto(&lTestVars.testObjectStatus2) + + It("Should DeepCopyInto the Group", func() { + Expect(lTestVars.testObjectStatus2).To(Equal(lTestVars.testObjectSpec1)) + }) + + t.Log("Success") +} diff --git a/apis/gitlab/v1alpha1/zz_generated.deepcopy.go b/apis/gitlab/v1alpha1/zz_generated.deepcopy.go index 9631bbc..dfa28ad 100644 --- a/apis/gitlab/v1alpha1/zz_generated.deepcopy.go +++ b/apis/gitlab/v1alpha1/zz_generated.deepcopy.go @@ -210,7 +210,7 @@ func (in *Group) DeepCopyInto(out *Group) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Group. @@ -266,7 +266,13 @@ func (in *GroupList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GroupSpec) DeepCopyInto(out *GroupSpec) { *out = *in - in.Projects.DeepCopyInto(&out.Projects) + if in.ProjectSpecs != nil { + in, out := &in.ProjectSpecs, &out.ProjectSpecs + *out = make([]ProjectSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupSpec. @@ -282,6 +288,13 @@ func (in *GroupSpec) DeepCopy() *GroupSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GroupStatus) DeepCopyInto(out *GroupStatus) { *out = *in + if in.GitlabId != nil { + in, out := &in.GitlabId, &out.GitlabId + *out = new(int64) + **out = **in + } + in.CreatedTime.DeepCopyInto(&out.CreatedTime) + in.LastUpdatedTime.DeepCopyInto(&out.LastUpdatedTime) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupStatus. diff --git a/config/crd/bases/customer.valkyrie.dso.mil_customers.yaml b/config/crd/bases/customer.valkyrie.dso.mil_customers.yaml index ff440d8..fe982e3 100644 --- a/config/crd/bases/customer.valkyrie.dso.mil_customers.yaml +++ b/config/crd/bases/customer.valkyrie.dso.mil_customers.yaml @@ -39,168 +39,71 @@ spec: Group: description: Group is the Gitlab Group that belongs to this Customer properties: - dummy: - description: test Dummy is an example field of Project. Edit project_types.go - to remove/update + gitlab_credentials_name: + description: GitlabCredentialsName is the name of the object in + this namespace that contains authentication information for + logging into the type: string name: description: Name is the name of the Group type: string projects:omitempty: - description: Projects are the GitLab Projects managed by this - Group - 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 - items: - items: - description: Project is the Schema for the projects 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: ProjectSpec defines the desired state of - Project - properties: - dummy: - description: test Dummy is an example field of Project. - Edit project_types.go to remove/update - type: string - impact_level: - description: ImpactLevel is the RMF Impact Level - for this Project - enum: - - 2 - - 4 - - 5 - - 6 - type: string - language: - description: Language is the programming language - of the Project for this application. - type: string - name: - description: Name is the name of this Project - type: string - path: - description: Path is the gitlab path for this Project - type: string - service_port: - description: ServicePort is the port for the virtual - service for the application served by this Project - format: int32 - type: integer - storage_types:omitempty: - description: StorageTypes is the storage types that - will be added to the Project - items: - type: string - type: array - virtual_service: - default: Default - description: VirtualService is the type of virtual - service that will be created for this Project - type: string - required: - - impact_level - - language - - name - - path - - storage_types:omitempty - - virtual_service - type: object - status: - description: ProjectStatus defines the observed state - of Project - properties: - dummy: - description: test Dummy is an example field of Project. - Edit project_types.go to remove/update - type: string - url: - description: Url is the url for the project in GitLab - type: string - required: - - url - type: object - type: object - type: array - 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: - description: ListMeta describes metadata that synthetic resources - must have, including lists and various status objects. A - resource may have only one of {ObjectMeta, ListMeta}. - properties: - continue: - description: continue may be set if the user set a limit - on the number of items returned, and indicates that - the server has more data available. The value is opaque - and may be used to issue another request to the endpoint - that served this list to retrieve the next set of available - objects. Continuing a consistent list may not be possible - if the server configuration has changed or more than - a few minutes have passed. The resourceVersion field - returned when using this continue value will be identical - to the value in the first response, unless you have - received this token from an error message. - type: string - remainingItemCount: - description: remainingItemCount is the number of subsequent - items in the list which are not included in this list - response. If the list request contained label or field - selectors, then the number of remaining items is unknown - and the field will be left unset and omitted during - serialization. If the list is complete (either because - it is not chunking or because this is the last chunk), - then there are no more remaining items and this field - will be left unset and omitted during serialization. - Servers older than v1.15 do not set this field. The - intended use of the remainingItemCount is *estimating* - the size of a collection. Clients should not rely on - the remainingItemCount to be set or to be exact. - format: int64 - type: integer - resourceVersion: - description: 'String that identifies the server''s internal - version of this object that can be used by clients to - determine when objects have changed. Value must be treated - as opaque by clients and passed unmodified back to the - server. Populated by the system. Read-only. More info: - https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + description: ProjectSpecs are for the GitLab projects managed + by this Group. It's expected that Projects will be managed + at the group level rather than be adjusted themselves. + items: + description: ProjectSpec defines the desired state of Project + properties: + dummy: + description: test Dummy is an example field of Project. + Edit project_types.go to remove/update + type: string + impact_level: + description: ImpactLevel is the RMF Impact Level for this + Project + enum: + - 2 + - 4 + - 5 + - 6 + type: string + language: + description: Language is the programming language of the + Project for this application. + type: string + name: + description: Name is the name of this Project + type: string + path: + description: Path is the gitlab path for this Project + type: string + service_port: + description: ServicePort is the port for the virtual service + for the application served by this Project + format: int32 + type: integer + storage_types:omitempty: + description: StorageTypes is the storage types that will + be added to the Project + items: type: string - selfLink: - description: "selfLink is a URL representing this object. - Populated by the system. Read-only. \n DEPRECATED Kubernetes - will stop propagating this field in 1.20 release and - the field is planned to be removed in 1.21 release." - type: string - type: object - required: - - items - type: object + type: array + virtual_service: + default: Default + description: VirtualService is the type of virtual service + that will be created for this Project + type: string + required: + - impact_level + - language + - name + - path + - storage_types:omitempty + - virtual_service + type: object + type: array required: + - gitlab_credentials_name - name - projects:omitempty type: object diff --git a/config/crd/bases/gitlab.valkyrie.dso.mil_groups.yaml b/config/crd/bases/gitlab.valkyrie.dso.mil_groups.yaml index 8d0fe20..81d87ee 100644 --- a/config/crd/bases/gitlab.valkyrie.dso.mil_groups.yaml +++ b/config/crd/bases/gitlab.valkyrie.dso.mil_groups.yaml @@ -36,173 +36,89 @@ spec: spec: description: GroupSpec defines the desired state of Group properties: - dummy: - description: test Dummy is an example field of Project. Edit project_types.go - to remove/update + gitlab_credentials_name: + description: GitlabCredentialsName is the name of the object in this + namespace that contains authentication information for logging into + the type: string name: description: Name is the name of the Group type: string projects:omitempty: - description: Projects are the GitLab Projects managed by this Group - 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 - items: - items: - description: Project is the Schema for the projects 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: ProjectSpec defines the desired state of Project - properties: - dummy: - description: test Dummy is an example field of Project. - Edit project_types.go to remove/update - type: string - impact_level: - description: ImpactLevel is the RMF Impact Level for - this Project - enum: - - 2 - - 4 - - 5 - - 6 - type: string - language: - description: Language is the programming language of - the Project for this application. - type: string - name: - description: Name is the name of this Project - type: string - path: - description: Path is the gitlab path for this Project - type: string - service_port: - description: ServicePort is the port for the virtual - service for the application served by this Project - format: int32 - type: integer - storage_types:omitempty: - description: StorageTypes is the storage types that - will be added to the Project - items: - type: string - type: array - virtual_service: - default: Default - description: VirtualService is the type of virtual service - that will be created for this Project - type: string - required: - - impact_level - - language - - name - - path - - storage_types:omitempty - - virtual_service - type: object - status: - description: ProjectStatus defines the observed state of - Project - properties: - dummy: - description: test Dummy is an example field of Project. - Edit project_types.go to remove/update - type: string - url: - description: Url is the url for the project in GitLab - type: string - required: - - url - type: object - type: object - type: array - 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: - description: ListMeta describes metadata that synthetic resources - must have, including lists and various status objects. A resource - may have only one of {ObjectMeta, ListMeta}. - properties: - continue: - description: continue may be set if the user set a limit on - the number of items returned, and indicates that the server - has more data available. The value is opaque and may be - used to issue another request to the endpoint that served - this list to retrieve the next set of available objects. - Continuing a consistent list may not be possible if the - server configuration has changed or more than a few minutes - have passed. The resourceVersion field returned when using - this continue value will be identical to the value in the - first response, unless you have received this token from - an error message. + description: ProjectSpecs are for the GitLab projects managed by this + Group. It's expected that Projects will be managed at the group + level rather than be adjusted themselves. + items: + description: ProjectSpec defines the desired state of Project + properties: + dummy: + description: test Dummy is an example field of Project. Edit + project_types.go to remove/update + type: string + impact_level: + description: ImpactLevel is the RMF Impact Level for this Project + enum: + - 2 + - 4 + - 5 + - 6 + type: string + language: + description: Language is the programming language of the Project + for this application. + type: string + name: + description: Name is the name of this Project + type: string + path: + description: Path is the gitlab path for this Project + type: string + service_port: + description: ServicePort is the port for the virtual service + for the application served by this Project + format: int32 + type: integer + storage_types:omitempty: + description: StorageTypes is the storage types that will be + added to the Project + items: type: string - remainingItemCount: - description: remainingItemCount is the number of subsequent - items in the list which are not included in this list response. - If the list request contained label or field selectors, - then the number of remaining items is unknown and the field - will be left unset and omitted during serialization. If - the list is complete (either because it is not chunking - or because this is the last chunk), then there are no more - remaining items and this field will be left unset and omitted - during serialization. Servers older than v1.15 do not set - this field. The intended use of the remainingItemCount is - *estimating* the size of a collection. Clients should not - rely on the remainingItemCount to be set or to be exact. - format: int64 - type: integer - resourceVersion: - description: 'String that identifies the server''s internal - version of this object that can be used by clients to determine - when objects have changed. Value must be treated as opaque - by clients and passed unmodified back to the server. Populated - by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - selfLink: - description: "selfLink is a URL representing this object. - Populated by the system. Read-only. \n DEPRECATED Kubernetes - will stop propagating this field in 1.20 release and the - field is planned to be removed in 1.21 release." - type: string - type: object - required: - - items - type: object + type: array + virtual_service: + default: Default + description: VirtualService is the type of virtual service that + will be created for this Project + type: string + required: + - impact_level + - language + - name + - path + - storage_types:omitempty + - virtual_service + type: object + type: array required: + - gitlab_credentials_name - name - projects:omitempty type: object status: description: GroupStatus defines the observed state of Group properties: - dummy: - description: test Dummy is an example field of Project. Edit project_types.go - to remove/update + created_time: + format: date-time type: string + gitlab_id: + format: int64 + type: integer + last_updated_time: + format: date-time + type: string + required: + - created_time + - gitlab_id + - last_updated_time type: object type: object served: true -- GitLab From fd7b1c33a5d564f205b76f7dbeec8d4012af4cd2 Mon Sep 17 00:00:00 2001 From: Jason van Brackel Date: Thu, 10 Jun 2021 12:44:34 -0400 Subject: [PATCH 03/15] WIP Groups Controller --- apis/gitlab/v1alpha1/group_types.go | 4 +- .../customer.valkyrie.dso.mil_customers.yaml | 5 +- .../bases/gitlab.valkyrie.dso.mil_groups.yaml | 8 +-- config/manager/kustomization.yaml | 10 +++- config/rbac/role.yaml | 16 ++++++ .../gitlab_v1alpha1_gitlabcredentials.yaml | 18 +++++-- config/samples/gitlab_v1alpha1_group.yaml | 6 ++- controllers/gitlab/group_controller.go | 49 +++++++++++++++++-- controllers/gitlab/group_controller_test.go | 27 ++++++++++ controllers/gitlab/mocks_test.go | 43 ++++++++++++++++ 10 files changed, 168 insertions(+), 18 deletions(-) diff --git a/apis/gitlab/v1alpha1/group_types.go b/apis/gitlab/v1alpha1/group_types.go index d6d625d..0a16499 100644 --- a/apis/gitlab/v1alpha1/group_types.go +++ b/apis/gitlab/v1alpha1/group_types.go @@ -28,10 +28,11 @@ type GroupSpec struct { // GitlabCredentialsName is the name of the object in this namespace that contains authentication // information for logging into the - GitlabCredentialsName string `json:"gitlab_credentials_name"` + GitlabCredentialsName string `json:"gitlabCredentialsName"` // ProjectSpecs are for the GitLab projects managed by this Group. It's expected that Projects // will be managed at the group level rather than be adjusted themselves. + // +kubebuilder:validation:Optional ProjectSpecs []ProjectSpec `json:"projects:omitempty"` } @@ -40,6 +41,7 @@ type GroupStatus struct { GitlabId *int64 `json:"gitlab_id"` CreatedTime metav1.Time `json:"created_time"` LastUpdatedTime metav1.Time `json:"last_updated_time"` + State string `json:"state"` } //+kubebuilder:object:root=true diff --git a/config/crd/bases/customer.valkyrie.dso.mil_customers.yaml b/config/crd/bases/customer.valkyrie.dso.mil_customers.yaml index fe982e3..943d3ba 100644 --- a/config/crd/bases/customer.valkyrie.dso.mil_customers.yaml +++ b/config/crd/bases/customer.valkyrie.dso.mil_customers.yaml @@ -39,7 +39,7 @@ spec: Group: description: Group is the Gitlab Group that belongs to this Customer properties: - gitlab_credentials_name: + gitlabCredentialsName: description: GitlabCredentialsName is the name of the object in this namespace that contains authentication information for logging into the @@ -103,9 +103,8 @@ spec: type: object type: array required: - - gitlab_credentials_name + - gitlabCredentialsName - name - - projects:omitempty type: object Organization: description: Organization is the organizational information for this diff --git a/config/crd/bases/gitlab.valkyrie.dso.mil_groups.yaml b/config/crd/bases/gitlab.valkyrie.dso.mil_groups.yaml index 81d87ee..ccbce45 100644 --- a/config/crd/bases/gitlab.valkyrie.dso.mil_groups.yaml +++ b/config/crd/bases/gitlab.valkyrie.dso.mil_groups.yaml @@ -36,7 +36,7 @@ spec: spec: description: GroupSpec defines the desired state of Group properties: - gitlab_credentials_name: + gitlabCredentialsName: description: GitlabCredentialsName is the name of the object in this namespace that contains authentication information for logging into the @@ -99,9 +99,8 @@ spec: type: object type: array required: - - gitlab_credentials_name + - gitlabCredentialsName - name - - projects:omitempty type: object status: description: GroupStatus defines the observed state of Group @@ -115,10 +114,13 @@ spec: last_updated_time: format: date-time type: string + state: + type: string required: - created_time - gitlab_id - last_updated_time + - state type: object type: object served: true diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 2bcd3ee..5e793dd 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,6 +5,12 @@ generatorOptions: disableNameSuffixHash: true configMapGenerator: -- name: manager-config - files: +- files: - controller_manager_config.yaml + name: manager-config +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +images: +- name: controller + newName: controller + newTag: latest diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index a7301ed..f6254bf 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -6,6 +6,22 @@ metadata: creationTimestamp: null name: manager-role rules: +- apiGroups: + - "" + resources: + - secret + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - secretreference + verbs: + - get + - list + - watch - apiGroups: - customer.valkyrie.dso.mil resources: diff --git a/config/samples/gitlab_v1alpha1_gitlabcredentials.yaml b/config/samples/gitlab_v1alpha1_gitlabcredentials.yaml index 5aa1eef..d70ac54 100644 --- a/config/samples/gitlab_v1alpha1_gitlabcredentials.yaml +++ b/config/samples/gitlab_v1alpha1_gitlabcredentials.yaml @@ -1,7 +1,19 @@ +apiVersion: v1 +kind: Secret +metadata: + name: gitlab-creds +type: Opaque +data: + accesstoken: cGFzc3dvcmQ= +--- apiVersion: gitlab.valkyrie.dso.mil/v1alpha1 kind: GitlabCredentials metadata: - name: gitlabcredentials-sample + name: gitlab-credentials + namespace: default spec: - # Add fields here - foo: bar + url: https://example.com + username: jvb + access_token: + namespace: default + name: gitlab-creds \ No newline at end of file diff --git a/config/samples/gitlab_v1alpha1_group.yaml b/config/samples/gitlab_v1alpha1_group.yaml index c7ac53d..546047e 100644 --- a/config/samples/gitlab_v1alpha1_group.yaml +++ b/config/samples/gitlab_v1alpha1_group.yaml @@ -2,6 +2,8 @@ apiVersion: gitlab.valkyrie.dso.mil/v1alpha1 kind: Group metadata: name: group-sample + namespace: default spec: - # Add fields here - foo: bar + name: newP1client + gitlabCredentialsName: gitlab-credentials + diff --git a/controllers/gitlab/group_controller.go b/controllers/gitlab/group_controller.go index 3abf448..280317c 100644 --- a/controllers/gitlab/group_controller.go +++ b/controllers/gitlab/group_controller.go @@ -18,12 +18,12 @@ package gitlab import ( "context" - "github.com/go-logr/logr" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" ) @@ -38,16 +38,57 @@ type GroupReconciler struct { //+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups/status,verbs=get;update;patch //+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups/finalizers,verbs=update +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=gitlabcredentials,verbs=get;list;watch +//+kubebuilder:rbac:groups=core,resources=secretreference,verbs=get;list;watch +//+kubebuilder:rbac:groups=core,resources=secret,verbs=get;list;watch +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=projects,verbs=get;list;watch;create;update;patch;delete // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. // The reconcile loop for the Gitlab Group will update the Group and any of the child Project API Objects. func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = r.Log.WithValues("group", req.NamespacedName) + log := r.Log.WithValues("group", req.NamespacedName) + var group gitlabv1alpha1.Group // Get the Group Object + if err := r.Get(ctx, req.NamespacedName, &group); err != nil { + log.Error(err, "unable to fetch group", "group", req.NamespacedName.Name) + // we'll ignore not-found errors, since they can't be fixed by an immediate + // requeue (we'll need to wait for a new notification), and we can get them + // on deleted requests. + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + // Get the Gitlab Credentials + var gitLabCredentialsName = types.NamespacedName{ + Namespace: req.Namespace, + Name: group.Spec.GitlabCredentialsName, + } + + var gitlabCredentials gitlabv1alpha1.GitlabCredentials + if err := r.Get(ctx, gitLabCredentialsName, &gitlabCredentials); err != nil { + log.Error(err, "unable to fetch gitlab credentails") + group.Status.State = "gitlab credential Not Found." + return ctrl.Result{Requeue: true}, err + } + + var secretName = types.NamespacedName{ + Namespace: gitlabCredentials.Spec.AccessToken.Namespace, + Name: gitlabCredentials.Spec.AccessToken.Name, + } + + var secret v1.Secret + // Get the Secret + if err := r.Get(ctx, secretName, &secret); err != nil { + log.Error(err, "unable to fetch secret from gitlab credentials") + group.Status.State = "secret from gitlab credential not found." + + return ctrl.Result{Requeue: true}, err + } + + var accessToken = string(secret.Data["accessToken"]) - // See that the secret exists + return ctrl.Result{}, nil } diff --git a/controllers/gitlab/group_controller_test.go b/controllers/gitlab/group_controller_test.go index c56e080..9aa1524 100755 --- a/controllers/gitlab/group_controller_test.go +++ b/controllers/gitlab/group_controller_test.go @@ -1,13 +1,40 @@ package gitlab import ( + "context" . "github.com/onsi/ginkgo" + ctrl "sigs.k8s.io/controller-runtime" + gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" ) var _ = Describe("group_controller", func() { Describe("GroupReconciler", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() Describe("Reconcile", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + + It("Should lookup the API object", func() { + sut.Reconcile(contextMock, requestMock) + + }) When("Getting ready to login", func() { + It("Should attempt to get the secretFrom the GitlabCredentials", func() { + + }) Context("Secret doesn't exist", func() { It("Should change the status to MissingCredentials", func() { //TODO diff --git a/controllers/gitlab/mocks_test.go b/controllers/gitlab/mocks_test.go index f8f839d..66be8d0 100755 --- a/controllers/gitlab/mocks_test.go +++ b/controllers/gitlab/mocks_test.go @@ -19,6 +19,49 @@ import ( "valkyrie.dso.mil/valkyrie-api/controllers" ) +type MockClient struct { +} + +func (m *MockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { + panic("implement me") +} + +func (m *MockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + panic("implement me") +} + +func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + panic("implement me") +} + +func (m *MockClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { + panic("implement me") +} + +func (m *MockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + panic("implement me") +} + +func (m *MockClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + panic("implement me") +} + +func (m *MockClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { + panic("implement me") +} + +func (m *MockClient) Status() client.StatusWriter { + panic("implement me") +} + +func (m *MockClient) Scheme() *runtime.Scheme { + panic("implement me") +} + +func (m *MockClient) RESTMapper() meta.RESTMapper { + panic("implement me") +} + type MockContext struct { } -- GitLab From 137aef8b3b3b01732f51a35b039883fe7f448d88 Mon Sep 17 00:00:00 2001 From: Jason van Brackel Date: Thu, 10 Jun 2021 13:33:40 -0400 Subject: [PATCH 04/15] WIP continue group controller work --- controllers/gitlab/group_controller.go | 10 +++++++--- go.mod | 1 + go.sum | 12 ++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/controllers/gitlab/group_controller.go b/controllers/gitlab/group_controller.go index 280317c..5bd7e7f 100644 --- a/controllers/gitlab/group_controller.go +++ b/controllers/gitlab/group_controller.go @@ -50,6 +50,7 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl log := r.Log.WithValues("group", req.NamespacedName) var group gitlabv1alpha1.Group + // Get the Group Object if err := r.Get(ctx, req.NamespacedName, &group); err != nil { log.Error(err, "unable to fetch group", "group", req.NamespacedName.Name) @@ -86,9 +87,12 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl return ctrl.Result{Requeue: true}, err } - var accessToken = string(secret.Data["accessToken"]) - - + //var accessToken = string(secret.Data["accessToken"]) + // + //var gitlabClient gitlab.Client + //if gitlabClient, err := gitlab.NewClient(accessToken, gitlab.WithBaseURL(gitlabCredentials.Spec.Url)); err != nil { + // log.Error(err, "unable to create gitlab client", "username", gitlabCredentials.Spec.Username, "url", gitlabCredentials.Spec.Url) + //} return ctrl.Result{}, nil } diff --git a/go.mod b/go.mod index 5c0efa9..d3c35e7 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/go-logr/logr v0.3.0 github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.13.0 + github.com/xanzy/go-gitlab v0.50.0 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect k8s.io/api v0.19.2 k8s.io/apimachinery v0.19.2 diff --git a/go.sum b/go.sum index 6e084c0..c14ab6c 100644 --- a/go.sum +++ b/go.sum @@ -198,6 +198,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -222,6 +224,12 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs= +github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= @@ -370,6 +378,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/xanzy/go-gitlab v0.50.0 h1:t7IoYTrnLSbdEZN7d8X/5zcr+ZM4TZQ2mXa8MqWlAZQ= +github.com/xanzy/go-gitlab v0.50.0/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -465,6 +475,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs= @@ -567,6 +578,7 @@ google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +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.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -- GitLab From 4c2c323cba0cdbffaf9c8da096e71cc6c34f9098 Mon Sep 17 00:00:00 2001 From: Jason van Brackel Date: Tue, 15 Jun 2021 09:55:19 -0400 Subject: [PATCH 05/15] feat: work on group controller --- controllers/gitlab/group_controller.go | 8 +- controllers/gitlab/group_controller_test.go | 306 +++++++++++++------- controllers/gitlab/mocks_test.go | 62 +++- go.mod | 1 + go.sum | 2 + 5 files changed, 265 insertions(+), 114 deletions(-) diff --git a/controllers/gitlab/group_controller.go b/controllers/gitlab/group_controller.go index 5bd7e7f..0d0433e 100644 --- a/controllers/gitlab/group_controller.go +++ b/controllers/gitlab/group_controller.go @@ -49,10 +49,10 @@ type GroupReconciler struct { func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := r.Log.WithValues("group", req.NamespacedName) - var group gitlabv1alpha1.Group + group := &gitlabv1alpha1.Group{} // Get the Group Object - if err := r.Get(ctx, req.NamespacedName, &group); err != nil { + if err := r.Get(ctx, req.NamespacedName, group); err != nil { log.Error(err, "unable to fetch group", "group", req.NamespacedName.Name) // we'll ignore not-found errors, since they can't be fixed by an immediate // requeue (we'll need to wait for a new notification), and we can get them @@ -68,7 +68,7 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl var gitlabCredentials gitlabv1alpha1.GitlabCredentials if err := r.Get(ctx, gitLabCredentialsName, &gitlabCredentials); err != nil { - log.Error(err, "unable to fetch gitlab credentails") + log.Error(err, "unable to fetch gitlab credentials") group.Status.State = "gitlab credential Not Found." return ctrl.Result{Requeue: true}, err } @@ -88,7 +88,7 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl } //var accessToken = string(secret.Data["accessToken"]) - // + //var gitlabClient gitlab.Client //if gitlabClient, err := gitlab.NewClient(accessToken, gitlab.WithBaseURL(gitlabCredentials.Spec.Url)); err != nil { // log.Error(err, "unable to create gitlab client", "username", gitlabCredentials.Spec.Username, "url", gitlabCredentials.Spec.Url) diff --git a/controllers/gitlab/group_controller_test.go b/controllers/gitlab/group_controller_test.go index 9aa1524..1561a0a 100755 --- a/controllers/gitlab/group_controller_test.go +++ b/controllers/gitlab/group_controller_test.go @@ -3,7 +3,12 @@ package gitlab import ( "context" . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" ) @@ -12,135 +17,226 @@ var _ = Describe("group_controller", func() { builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) scheme, _ := builder.Build() Describe("Reconcile", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - - It("Should lookup the API object", func() { - sut.Reconcile(contextMock, requestMock) - - }) - When("Getting ready to login", func() { - It("Should attempt to get the secretFrom the GitlabCredentials", func() { - - }) - Context("Secret doesn't exist", func() { - It("Should change the status to MissingCredentials", func() { - //TODO - }) - It("Should log an error regarding the missing credentials", func() { - // TODO - }) - It("Should return an error", func() { - //TODO + When("it looks up the API Object", func() { + Context("the API Object isn't found", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{} + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch group")) + }) + It("should return an empty result, and ignore the error.", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).Should(BeNil()) }) }) - It("Should log that it's going to log in with credentials (account token masked)", func() { - //TODO - }) }) - When("Logging in to GitLab", func() { - It("Should log the attempt to login", func() { - // TODO - }) - Context("Unsuccessful login", func() { - It("Should log the login failure", func() { - // TODO - }) - It("Should return an error", func() { - // TODO - }) - }) - Context("Server Error", func() { - It("Should log the error", func() { - // TODO - }) - It("Should return the error", func() { + When("it looks up the GitlabCredentals", func() { + Context("gitlab credentials are not found", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + clientMock.GetFunction = nil + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{}, + Status: gitlabv1alpha1.GroupStatus{}, + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + result, err := sut.Reconcile(contextMock, requestMock) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch gitlab credentials")) + }) + It("should return a reconcile result, and the error.", func() { + Expect(result).To(Equal(ctrl.Result{ + Requeue: true, + })) + Expect(err).ToNot(BeNil()) + }) + }) + When("getting the secret from the GitlabCredentials", func() { + Context("Secret doesn't exist", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + Url: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + result, err := sut.Reconcile(contextMock, requestMock) + It("Should log an error regarding the missing credentials", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch secret from gitlab credentials")) + }) + It("Should return a requeue result and an error", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + Expect(err).ToNot(BeNil()) + }) + }) + }) + When("Logging in to GitLab", func() { + + Context("Unsuccessful login", func() { + It("Should log the login failure", func() { + // TODO + }) + It("Should return an error", func() { + // TODO + }) + }) + Context("Server Error", func() { + It("Should log the error", func() { + // TODO + }) + It("Should return the error", func() { + // TODO + }) + }) + It("Should log the successful login", func() { // TODO }) }) - It("Should log the successful login", func() { - // TODO - }) - }) - When("Reconciling the state of the Group", func() { - It("Should log that it's grabbing the group from the Kubernetes API", func() { - // TODO - }) - It("Should get the Group Object from the Kubernetes API", func() { - // TODO - }) - Context("Fails to get the group", func() { - It("Should log the error.", func() { + When("Reconciling the state of the Group", func() { + It("Should log that it's grabbing the group from the Kubernetes API", func() { // TODO }) - It("Should return an error", func() { + It("Should get the Group Object from the Kubernetes API", func() { // TODO }) - }) - It("Should log that it's grabbing the group from the Gitlab API", func() { - // TODO - }) - Context("Account has insufficient permissions", func() { - It("Should log the error", func() { - // TODO + Context("Fails to get the group", func() { + It("Should log the error.", func() { + // TODO + }) + It("Should return an error", func() { + // TODO + }) }) - It("Should return an error", func() { + It("Should log that it's grabbing the group from the Gitlab API", func() { // TODO }) - }) - Context("Server Error", func() { - It("Should log the error", func() { + Context("Account has insufficient permissions", func() { + It("Should log the error", func() { + // TODO + }) + It("Should return an error", func() { + // TODO + }) + }) + Context("Server Error", func() { + It("Should log the error", func() { + // TODO + }) + It("Should return the error", func() { + // TODO + }) + }) + It("Should log that it's updating the Gitlab Group", func() { // TODO }) - It("Should return the error", func() { + It("Should update Gitlab Group using the Gitlab API", func() { // TODO }) - }) - It("Should log that it's updating the Gitlab Group", func() { - // TODO - }) - It("Should update Gitlab Group using the Gitlab API", func() { - // TODO - }) - Context("Account has insufficient permissions", func() { - It("Should log the error", func() { + Context("Account has insufficient permissions", func() { + It("Should log the error", func() { + // TODO + }) + It("Should return an error", func() { + // TODO + }) + }) + Context("Server Error", func() { + It("Should log the error", func() { + // TODO + }) + It("Should return the error", func() { + // TODO + }) + }) + It("Should update the Group Object in the Kubernetes API", func() { // TODO }) - It("Should return an error", func() { + It("Should log successful update", func() { // TODO }) - }) - Context("Server Error", func() { - It("Should log the error", func() { + It("Should return success", func() { // TODO }) - It("Should return the error", func() { - // TODO - }) - }) - It("Should update the Group Object in the Kubernetes API", func() { - // TODO - }) - It("Should log successful update", func() { - // TODO - }) - It("Should return success", func() { - // TODO - }) - // TODO, not sure what to do about Projects under the group yet, which is why I'm noting that here. + // TODO, not sure what to do about Projects under the group yet, which is why I'm noting that here. + }) }) }) }) diff --git a/controllers/gitlab/mocks_test.go b/controllers/gitlab/mocks_test.go index 66be8d0..6e55b7e 100755 --- a/controllers/gitlab/mocks_test.go +++ b/controllers/gitlab/mocks_test.go @@ -3,7 +3,10 @@ package gitlab import ( "context" "github.com/go-logr/logr" + "github.com/jinzhu/copier" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" "k8s.io/client-go/tools/record" @@ -20,10 +23,41 @@ import ( ) type MockClient struct { + GetFunction func(ctx context.Context, key client.ObjectKey, obj client.Object) error + GetCalled bool + expectedObjects map[client.ObjectKey]client.Object + NotFoundError error } func (m *MockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { - panic("implement me") + m.GetCalled = true + if m.GetFunction != nil { + return m.GetFunction(ctx, key, obj) + } + + if m.expectedObjects == nil { + return nil + } + + if m.expectedObjects[key] == nil { + return &errors.StatusError{ + ErrStatus: metav1.Status{ + TypeMeta: metav1.TypeMeta{}, + ListMeta: metav1.ListMeta{}, + Status: string(metav1.StatusReasonNotFound), + Message: "NotFound", + Reason: metav1.StatusReasonNotFound, + Details: nil, + Code: 0, + }, + } + } + + foundObject := m.expectedObjects[key] + + copier.Copy(obj, foundObject) + + return nil } func (m *MockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { @@ -86,22 +120,40 @@ type MockLogger struct { WithValuesCalled bool WithNameValue string WithNameCalled bool + loggedMessage string + keysAndValues []interface{} + logFunction func(msg string, keysAndValues ...interface{}) + logLevelCalled string + level int } func (m *MockLogger) Enabled() bool { - panic("implement me") + return true } func (m *MockLogger) Info(msg string, keysAndValues ...interface{}) { - panic("implement me") + m.loggedMessage = msg + m.keysAndValues = keysAndValues + m.logLevelCalled = "Info" + if m.logFunction != nil { + m.logFunction(msg, keysAndValues) + return + } } func (m *MockLogger) Error(err error, msg string, keysAndValues ...interface{}) { - panic("implement me") + m.loggedMessage = msg + m.keysAndValues = keysAndValues + m.logLevelCalled = "Error" + if m.logFunction != nil { + m.logFunction(msg, keysAndValues) + return + } } func (m *MockLogger) V(level int) logr.Logger { - panic("implement me") + m.level = level + return m } func (m *MockLogger) WithValues(keysAndValues ...interface{}) logr.Logger { diff --git a/go.mod b/go.mod index d3c35e7..ddb1b82 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.15 require ( github.com/go-logr/logr v0.3.0 + github.com/jinzhu/copier v0.3.2 github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.13.0 github.com/xanzy/go-gitlab v0.50.0 diff --git a/go.sum b/go.sum index c14ab6c..4f512bb 100644 --- a/go.sum +++ b/go.sum @@ -241,6 +241,8 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ github.com/imdario/mergo v0.3.10 h1:6q5mVkdH/vYmqngx7kZQTjJ5HRsx+ImorDIEQ+beJgc= github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jinzhu/copier v0.3.2 h1:QdBOCbaouLDYaIPFfi1bKv5F5tPpeTwXe4sD0jqtz5w= +github.com/jinzhu/copier v0.3.2/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -- GitLab From b9e964d5d4379619d37bd53dbc4bab7f6ba785da Mon Sep 17 00:00:00 2001 From: Jason van Brackel Date: Tue, 15 Jun 2021 15:07:34 -0400 Subject: [PATCH 06/15] feat: gitlab controller logic --- controllers/gitlab/gitlab_client.go | 9 + controllers/gitlab/group_controller.go | 163 +++++++++- controllers/gitlab/group_controller_test.go | 340 ++++++++++---------- controllers/gitlab/mocks_test.go | 14 + go.mod | 1 + go.sum | 12 + 6 files changed, 360 insertions(+), 179 deletions(-) create mode 100644 controllers/gitlab/gitlab_client.go diff --git a/controllers/gitlab/gitlab_client.go b/controllers/gitlab/gitlab_client.go new file mode 100644 index 0000000..e6df959 --- /dev/null +++ b/controllers/gitlab/gitlab_client.go @@ -0,0 +1,9 @@ +package gitlab + +import . "github.com/xanzy/go-gitlab" + +type GitlabClient interface { + NewClient(token string, options ...ClientOptionFunc) (*Client, error) + ListGroups(opt *ListGroupsOptions, options ...RequestOptionFunc) ([]*Group, *Response, error) +} + diff --git a/controllers/gitlab/group_controller.go b/controllers/gitlab/group_controller.go index 0d0433e..b2102c2 100644 --- a/controllers/gitlab/group_controller.go +++ b/controllers/gitlab/group_controller.go @@ -18,12 +18,16 @@ package gitlab import ( "context" + "fmt" "github.com/go-logr/logr" + "github.com/xanzy/go-gitlab" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "time" gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" ) @@ -33,6 +37,7 @@ type GroupReconciler struct { client.Client Log logr.Logger Scheme *runtime.Scheme + gitlabClient GitlabClient } //+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups,verbs=get;list;watch;create;update;patch;delete @@ -87,19 +92,167 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl return ctrl.Result{Requeue: true}, err } - //var accessToken = string(secret.Data["accessToken"]) + // Login to Gitlab + var accessToken = string(secret.Data["accessToken"]) - //var gitlabClient gitlab.Client - //if gitlabClient, err := gitlab.NewClient(accessToken, gitlab.WithBaseURL(gitlabCredentials.Spec.Url)); err != nil { - // log.Error(err, "unable to create gitlab client", "username", gitlabCredentials.Spec.Username, "url", gitlabCredentials.Spec.Url) - //} + var gitlabClient *gitlab.Client + var err error + if gitlabClient, err = gitlab.NewClient(accessToken, gitlab.WithBaseURL(gitlabCredentials.Spec.Url)); err != nil { + log.Error(err, "unable to create gitlab client", "username", gitlabCredentials.Spec.Username, "url", gitlabCredentials.Spec.Url) + return ctrl.Result{Requeue: true}, err + } + + // See if the Group Exists + var gitlabGroup *gitlab.Group + found, response, gitlabGroup, err := r.groupExists(gitlabClient, group) + if err != nil { + log.Error(err, "Error while searching groups.", "status", response.Status, "statusCode", response.StatusCode) + return ctrl.Result{Requeue: true}, err + } + + if !found { + gitlabGroup, response, err = r.createGroup(gitlabClient, group) + } + + if err := r.UpdateStatus(ctx, gitlabGroup.ID, group); err != nil { + log.Error(err, "Unable to update status.", "group", group) + return ctrl.Result{ Requeue: true }, err + } + + if err := r.processProjects(ctx, group, !found, group.Spec.ProjectSpecs); err != nil { + log.Error(err, "Unable to process projects", "group", group) + return ctrl.Result{Requeue: true}, err + } return ctrl.Result{}, nil } +func (r *GroupReconciler) groupExists(gitlabClient *gitlab.Client, group *gitlabv1alpha1.Group) (bool, *gitlab.Response, *gitlab.Group, error) { + + var grouplist []*gitlab.Group + var response *gitlab.Response + var err error + if grouplist, response, err = gitlabClient.Groups.ListGroups( + &gitlab.ListGroupsOptions{ + Search: &group.Spec.Name, + }); err != nil { + return false, response, nil, err + } + + // TODO: (jvb) For work beyond the MVP we'll need to handle pagination. + found := false + for _, groupInList := range grouplist { + found = group.Name == groupInList.Name + if found { + return found, response, groupInList, nil + } + } + return found, response, nil, nil +} + // SetupWithManager sets up the controller with the Manager. func (r *GroupReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&gitlabv1alpha1.Group{}). + Owns(&gitlabv1alpha1.Project{}). Complete(r) } + +func (r *GroupReconciler) createGroup(client *gitlab.Client, group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { + opt := gitlab.CreateGroupOptions{ + Name: &group.Spec.Name, + } + gitlabGroup, response, err := client.Groups.CreateGroup(&opt) + group.Status.CreatedTime = metav1.Time{ + Time: time.Now(), + } + group.ObjectMeta.Annotations["ID"] = string(gitlabGroup.ID) + group.Status.State = "Created" + return gitlabGroup, response, err +} + +func (r *GroupReconciler) processProjects(ctx context.Context, group *gitlabv1alpha1.Group, newProject bool, specs []gitlabv1alpha1.ProjectSpec) error { + for _, projectSpec := range specs { + if newProject { + if err := r.createProject(ctx, group, projectSpec); err != nil { + r.Log.Error(err, "Error trying to create project", "project", projectSpec) + return err + } + } else { + if project, foundProject, err := r.getProject(ctx, group, projectSpec); err != nil { + r.Log.Error(err,"Error looking up project", "name", projectSpec.Name) + return err + } else if foundProject { + if _, err := r.updateProject(ctx,group, projectSpec, project); err != nil { + r.Log.Error(err, "Error updating project", "project", project) + } + } + } + } + return nil +} + +func (r *GroupReconciler) getProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec) (*gitlabv1alpha1.Project, bool, error) { + var project gitlabv1alpha1.Project + projectName := types.NamespacedName{ + Namespace: group.Namespace, + Name: fmt.Sprintf("%s-%s", group.Name, spec.Name), + } + err := r.Get(ctx, projectName, &project) + + projectNotFound := client.IgnoreNotFound(err) + + if projectNotFound == nil { + return nil, false, nil + } + + return &project, true, err +} + +func (r *GroupReconciler) createProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec) error { + blockOwnerDeletion := true + managingController := true + return r.Create(ctx, &gitlabv1alpha1.Project{ + ObjectMeta: ctrl.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: group.APIVersion, + Kind: group.Kind, + Name: group.Name, + UID: group.UID, + Controller: &managingController, + BlockOwnerDeletion: &blockOwnerDeletion, + }, + }, + }, + Spec: gitlabv1alpha1.ProjectSpec{ + Name: spec.Name, + Path: spec.Path, + ImpactLevel: spec.ImpactLevel, + StorageTypes: spec.StorageTypes, + VirtualService: spec.VirtualService, + ServicePort: spec.ServicePort, + Language: spec.Language, + }, + }) +} + +func (r *GroupReconciler) updateProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec, project *gitlabv1alpha1.Project) (*gitlabv1alpha1.Project, error) { + project.Spec.Name = spec.Name + project.Spec.Path = spec.Path + project.Spec.ImpactLevel = spec.ImpactLevel + project.Spec.StorageTypes = spec.StorageTypes + project.Spec.VirtualService = spec.VirtualService + project.Spec.ServicePort = spec.ServicePort + project.Spec.Language = spec.Language + return project, r.Update(ctx, project) +} + +func (r *GroupReconciler) UpdateStatus(ctx context.Context, id int, group *gitlabv1alpha1.Group) error { + id64 := int64(id) + group.Status.GitlabId = &id64 + group.Status.LastUpdatedTime = metav1.Time{ + Time: time.Now(), + } + return r.Status().Update(ctx, group) +} diff --git a/controllers/gitlab/group_controller_test.go b/controllers/gitlab/group_controller_test.go index 1561a0a..09e0424 100755 --- a/controllers/gitlab/group_controller_test.go +++ b/controllers/gitlab/group_controller_test.go @@ -4,6 +4,7 @@ import ( "context" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/xanzy/go-gitlab" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -12,13 +13,83 @@ import ( gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" ) -var _ = Describe("group_controller", func() { - Describe("GroupReconciler", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - Describe("Reconcile", func() { - When("it looks up the API Object", func() { - Context("the API Object isn't found", func() { + +var _ = +Describe("group_controller", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + Describe("Reconcile", func() { + When("it looks up the API Object", func() { + Context("the API Object isn't found", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{} + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch group")) + }) + It("should return an empty result, and ignore the error.", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).Should(BeNil()) + }) + }) + }) + When("it looks up the GitlabCredentals", func() { + Context("gitlab credentials are not found", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + clientMock.GetFunction = nil + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{}, + Status: gitlabv1alpha1.GroupStatus{}, + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + result, err := sut.Reconcile(contextMock, requestMock) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch gitlab credentials")) + }) + It("should return a reconcile result, and the error.", func() { + Expect(result).To(Equal(ctrl.Result{ + Requeue: true, + })) + Expect(err).ToNot(BeNil()) + }) + }) + When("getting the secret from the GitlabCredentials", func() { + Context("Secret doesn't exist", func() { loggerMock := MockLogger{ WithValuesKeysAndValues: nil, WithValuesCalled: false, @@ -27,26 +98,59 @@ var _ = Describe("group_controller", func() { } contextMock := context.TODO() clientMock := MockClient{} - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) requestMock := ctrl.Request{} sut := GroupReconciler{ Client: &clientMock, Log: &loggerMock, Scheme: scheme, } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + Url: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred result, err := sut.Reconcile(contextMock, requestMock) - It("should log the error", func() { + It("Should log an error regarding the missing credentials", func() { Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch group")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch secret from gitlab credentials")) }) - It("should return an empty result, and ignore the error.", func() { - Expect(result).Should(Equal(ctrl.Result{})) - Expect(err).Should(BeNil()) + It("Should return a requeue result and an error", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + Expect(err).ToNot(BeNil()) }) }) }) - When("it looks up the GitlabCredentals", func() { - Context("gitlab credentials are not found", func() { + When("Logging in to GitLab", func() { + Context("can't create client", func() { loggerMock := MockLogger{ WithValuesKeysAndValues: nil, WithValuesCalled: false, @@ -65,178 +169,66 @@ var _ = Describe("group_controller", func() { Namespace: "default", Name: "testGroup", } - clientMock.GetFunction = nil + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + Url: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } group := gitlabv1alpha1.Group{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{}, - Status: gitlabv1alpha1.GroupStatus{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", } clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return nil, &MockError{ + message: "mocked error", + } + }, + } result, err := sut.Reconcile(contextMock, requestMock) - It("should log the error", func() { + It("Should log the failure", func() { Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch gitlab credentials")) + Expect(loggerMock.loggedMessage).To(Equal("unable to create gitlab client")) }) - It("should return a reconcile result, and the error.", func() { - Expect(result).To(Equal(ctrl.Result{ - Requeue: true, - })) + It("Should return an error", func() { Expect(err).ToNot(BeNil()) }) - }) - When("getting the secret from the GitlabCredentials", func() { - Context("Secret doesn't exist", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - Url: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - result, err := sut.Reconcile(contextMock, requestMock) - It("Should log an error regarding the missing credentials", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch secret from gitlab credentials")) - }) - It("Should return a requeue result and an error", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - Expect(err).ToNot(BeNil()) - }) - }) - }) - When("Logging in to GitLab", func() { - - Context("Unsuccessful login", func() { - It("Should log the login failure", func() { - // TODO - }) - It("Should return an error", func() { - // TODO - }) - }) - Context("Server Error", func() { - It("Should log the error", func() { - // TODO - }) - It("Should return the error", func() { - // TODO - }) - }) - It("Should log the successful login", func() { - // TODO + It("should requeue the group", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) }) }) - When("Reconciling the state of the Group", func() { - It("Should log that it's grabbing the group from the Kubernetes API", func() { - // TODO - }) - It("Should get the Group Object from the Kubernetes API", func() { - // TODO - }) - Context("Fails to get the group", func() { - It("Should log the error.", func() { - // TODO - }) - It("Should return an error", func() { - // TODO - }) - }) - It("Should log that it's grabbing the group from the Gitlab API", func() { - // TODO - }) - Context("Account has insufficient permissions", func() { - It("Should log the error", func() { - // TODO - }) - It("Should return an error", func() { - // TODO - }) - }) - Context("Server Error", func() { - It("Should log the error", func() { - // TODO - }) - It("Should return the error", func() { - // TODO - }) - }) - It("Should log that it's updating the Gitlab Group", func() { - // TODO - }) - It("Should update Gitlab Group using the Gitlab API", func() { - // TODO - }) - Context("Account has insufficient permissions", func() { - It("Should log the error", func() { - // TODO - }) - It("Should return an error", func() { - // TODO - }) - }) - Context("Server Error", func() { - It("Should log the error", func() { - // TODO - }) - It("Should return the error", func() { - // TODO - }) - }) - It("Should update the Group Object in the Kubernetes API", func() { - // TODO - }) - It("Should log successful update", func() { - // TODO - }) - It("Should return success", func() { - // TODO - }) - - // TODO, not sure what to do about Projects under the group yet, which is why I'm noting that here. - }) }) }) }) diff --git a/controllers/gitlab/mocks_test.go b/controllers/gitlab/mocks_test.go index 6e55b7e..b868aa2 100755 --- a/controllers/gitlab/mocks_test.go +++ b/controllers/gitlab/mocks_test.go @@ -4,6 +4,7 @@ import ( "context" "github.com/go-logr/logr" "github.com/jinzhu/copier" + "github.com/xanzy/go-gitlab" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -369,3 +370,16 @@ func (m *MockError) Error() string { } return m.message } + +type MockGitlabClient struct { + newClientFunction func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) + listGroupsFunction func(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) +} + +func (m *MockGitlabClient) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return m.newClientFunction(token, options...) +} + +func (m *MockGitlabClient) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { + return m.listGroupsFunction(opt, options...) +} diff --git a/go.mod b/go.mod index 536dbc0..bfcfe41 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/jinzhu/copier v0.3.2 github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.13.0 + github.com/xanzy/go-gitlab v0.50.0 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect k8s.io/api v0.19.2 k8s.io/apimachinery v0.19.2 diff --git a/go.sum b/go.sum index 824c6a4..e7e213c 100644 --- a/go.sum +++ b/go.sum @@ -198,6 +198,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -222,6 +224,12 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs= +github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= @@ -372,6 +380,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/xanzy/go-gitlab v0.50.0 h1:t7IoYTrnLSbdEZN7d8X/5zcr+ZM4TZQ2mXa8MqWlAZQ= +github.com/xanzy/go-gitlab v0.50.0/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -466,6 +476,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs= @@ -568,6 +579,7 @@ google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +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.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -- GitLab From 58f4cc03475fc976c3a42af86f555370e49cf795 Mon Sep 17 00:00:00 2001 From: Jason van Brackel Date: Tue, 15 Jun 2021 17:23:07 -0400 Subject: [PATCH 07/15] feat: add group owner and description --- apis/gitlab/v1alpha1/group_types.go | 16 +- apis/gitlab/v1alpha1/group_types_test.go | 2 + controllers/gitlab/gitlab_client.go | 1 + controllers/gitlab/group_controller.go | 108 ++++++-- controllers/gitlab/group_controller_test.go | 270 ++++++++++---------- 5 files changed, 239 insertions(+), 158 deletions(-) diff --git a/apis/gitlab/v1alpha1/group_types.go b/apis/gitlab/v1alpha1/group_types.go index 0a16499..8ece16d 100644 --- a/apis/gitlab/v1alpha1/group_types.go +++ b/apis/gitlab/v1alpha1/group_types.go @@ -22,10 +22,18 @@ import ( // GroupSpec defines the desired state of Group type GroupSpec struct { - // Name is the name of the Group + // Name is the name of the Group and will be used as part of the Url in Gitlab // +kubebuilder:validation:required Name string `json:"name"` + // Description is the Gitlab Description for the group + // +kubebuilder:validation:Optional + Description string `json:"description"` + + // Owner is the Gitlab Username for the group owner + // +kubebuilder:validation:Optional + Owner string `json:"owner"` + // GitlabCredentialsName is the name of the object in this namespace that contains authentication // information for logging into the GitlabCredentialsName string `json:"gitlabCredentialsName"` @@ -38,9 +46,9 @@ type GroupSpec struct { // GroupStatus defines the observed state of Group type GroupStatus struct { - GitlabId *int64 `json:"gitlab_id"` - CreatedTime metav1.Time `json:"created_time"` - LastUpdatedTime metav1.Time `json:"last_updated_time"` + GitlabId *int64 `json:"gitlabId"` + CreatedTime metav1.Time `json:"createdTime"` + LastUpdatedTime metav1.Time `json:"lastUpdatedTime"` State string `json:"state"` } diff --git a/apis/gitlab/v1alpha1/group_types_test.go b/apis/gitlab/v1alpha1/group_types_test.go index 0945cb5..63b4438 100644 --- a/apis/gitlab/v1alpha1/group_types_test.go +++ b/apis/gitlab/v1alpha1/group_types_test.go @@ -70,11 +70,13 @@ func initVarsGroup() testVarsGroup { testVars.testObjectSpec1 = GroupSpec{ Name: "testGroup1", GitlabCredentialsName: "nameOfTheCredentials", + Description: "testDescription1", } testVars.testObjectSpec2 = GroupSpec{ Name: "testGroup2", GitlabCredentialsName: "nameOfCredentials2", ProjectSpecs: nil, + Description: "testDescription2", } id1 := int64(1) diff --git a/controllers/gitlab/gitlab_client.go b/controllers/gitlab/gitlab_client.go index e6df959..ad13806 100644 --- a/controllers/gitlab/gitlab_client.go +++ b/controllers/gitlab/gitlab_client.go @@ -7,3 +7,4 @@ type GitlabClient interface { ListGroups(opt *ListGroupsOptions, options ...RequestOptionFunc) ([]*Group, *Response, error) } + diff --git a/controllers/gitlab/group_controller.go b/controllers/gitlab/group_controller.go index b2102c2..37cdc6f 100644 --- a/controllers/gitlab/group_controller.go +++ b/controllers/gitlab/group_controller.go @@ -31,6 +31,13 @@ import ( gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" ) + + +const errorUnableToFetchGroup = "unable to fetch group" +const errorUnableToFetchGitlabCredentials = "unable to fetch gitlab credentials" +const errorUnableToFetchSecret = "unable to fetch secret from gitlab credentials" +const errorUnableToCreateGitlabClient = "unable to create gitlab client" + // GroupReconciler reconciles the state of a Gitlab Group, for each reconciliation loop it will log in to Gitlab // with credentials in the secret provided. It will then check the state of the Group, and create it if necessary. type GroupReconciler struct { @@ -58,7 +65,7 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl // Get the Group Object if err := r.Get(ctx, req.NamespacedName, group); err != nil { - log.Error(err, "unable to fetch group", "group", req.NamespacedName.Name) + log.Error(err, errorUnableToFetchGroup, "group", req.NamespacedName.Name) // we'll ignore not-found errors, since they can't be fixed by an immediate // requeue (we'll need to wait for a new notification), and we can get them // on deleted requests. @@ -73,8 +80,9 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl var gitlabCredentials gitlabv1alpha1.GitlabCredentials if err := r.Get(ctx, gitLabCredentialsName, &gitlabCredentials); err != nil { - log.Error(err, "unable to fetch gitlab credentials") - group.Status.State = "gitlab credential Not Found." + + log.Error(err, errorUnableToFetchGitlabCredentials) + // TODO: (jvb) group.Status.State = "gitlab credential Not Found." return ctrl.Result{Requeue: true}, err } @@ -86,19 +94,22 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl var secret v1.Secret // Get the Secret if err := r.Get(ctx, secretName, &secret); err != nil { - log.Error(err, "unable to fetch secret from gitlab credentials") - group.Status.State = "secret from gitlab credential not found." + + log.Error(err, errorUnableToFetchSecret) + // TODO: (jvb) group.Status.State = "secret from gitlab credential not found." return ctrl.Result{Requeue: true}, err } // Login to Gitlab + //TODO Error for not found. + //TODO SecretKeyRef var accessToken = string(secret.Data["accessToken"]) var gitlabClient *gitlab.Client var err error if gitlabClient, err = gitlab.NewClient(accessToken, gitlab.WithBaseURL(gitlabCredentials.Spec.Url)); err != nil { - log.Error(err, "unable to create gitlab client", "username", gitlabCredentials.Spec.Username, "url", gitlabCredentials.Spec.Url) + log.Error(err, errorUnableToCreateGitlabClient, "username", gitlabCredentials.Spec.Username, "url", gitlabCredentials.Spec.Url) return ctrl.Result{Requeue: true}, err } @@ -111,10 +122,18 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl } if !found { - gitlabGroup, response, err = r.createGroup(gitlabClient, group) + if gitlabGroup, response, err = r.createGroup(gitlabClient, group); err != nil { + log.Error(err,"Error while creating GitLab group.", "group", group) + return ctrl.Result{Requeue: true}, err + } + } else { + if gitlabGroup, response, err = r.updateGroup(gitlabClient, group); err != nil { + log.Error(err, "Error while updating GitLab group.", "group", group) + return ctrl.Result{Requeue: true}, err + } } - if err := r.UpdateStatus(ctx, gitlabGroup.ID, group); err != nil { + if err := r.updateStatus(ctx, gitlabGroup.ID, group); err != nil { log.Error(err, "Unable to update status.", "group", group) return ctrl.Result{ Requeue: true }, err } @@ -150,27 +169,70 @@ func (r *GroupReconciler) groupExists(gitlabClient *gitlab.Client, group *gitlab return found, response, nil, nil } -// SetupWithManager sets up the controller with the Manager. -func (r *GroupReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&gitlabv1alpha1.Group{}). - Owns(&gitlabv1alpha1.Project{}). - Complete(r) -} - func (r *GroupReconciler) createGroup(client *gitlab.Client, group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { opt := gitlab.CreateGroupOptions{ Name: &group.Spec.Name, + Description: &group.Spec.Description, } gitlabGroup, response, err := client.Groups.CreateGroup(&opt) + r.reconcileGroupMembers(client, gitlabGroup, group) group.Status.CreatedTime = metav1.Time{ Time: time.Now(), } - group.ObjectMeta.Annotations["ID"] = string(gitlabGroup.ID) - group.Status.State = "Created" + group.ObjectMeta.Annotations["ID"] = fmt.Sprint(gitlabGroup.ID) + group.ObjectMeta.Annotations["Creator"] = "Valkyrie" // TODO Const + // TODO: (jvb) group.Status.State = "Created" // TODO Const + return gitlabGroup, response, err +} + +func (r *GroupReconciler) updateGroup(client *gitlab.Client, group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { + opt := gitlab.UpdateGroupOptions{ + Name: &group.Spec.Name, + Description: &group.Spec.Description, + } + gitlabGroup, response, err := client.Groups.UpdateGroup(group.Annotations["ID"], &opt) + r.reconcileGroupMembers(client, gitlabGroup, group) + group.Status.LastUpdatedTime = metav1.Time{ + Time: time.Now(), + } + return gitlabGroup, response, err } +func (r *GroupReconciler) reconcileGroupMembers(client *gitlab.Client, gitlabGroup *gitlab.Group, group *gitlabv1alpha1.Group) error { + listGroupOptions := &gitlab.ListGroupMembersOptions{} + var groupMembers []*gitlab.GroupMember + var response *gitlab.Response + var err error + if groupMembers, response, err = client.Groups.ListGroupMembers(gitlabGroup.ID, listGroupOptions); err != nil { + r.Log.Error(err, "Error while listing group members.", "group", group, "response", response) + return err + } + found := false + var foundMember *gitlab.GroupMember + for _, groupMember := range groupMembers { + found = groupMember.Username == group.Spec.Owner + if found { + foundMember = groupMember + break + } + } + + if !found { + owner := gitlab.OwnerPermissions + addGroupMemberOptions := gitlab.AddGroupMemberOptions{ + UserID: &foundMember.ID, + AccessLevel: &owner, + } + + if _, response, err = client.GroupMembers.AddGroupMember(gitlabGroup.ID, &addGroupMemberOptions); err != nil { + r.Log.Error(err, "Error while listing group members.", "group", group, "response", response, "userId", foundMember.ID) + return err + } + } + return nil +} + func (r *GroupReconciler) processProjects(ctx context.Context, group *gitlabv1alpha1.Group, newProject bool, specs []gitlabv1alpha1.ProjectSpec) error { for _, projectSpec := range specs { if newProject { @@ -248,7 +310,7 @@ func (r *GroupReconciler) updateProject(ctx context.Context, group *gitlabv1alph return project, r.Update(ctx, project) } -func (r *GroupReconciler) UpdateStatus(ctx context.Context, id int, group *gitlabv1alpha1.Group) error { +func (r *GroupReconciler) updateStatus(ctx context.Context, id int, group *gitlabv1alpha1.Group) error { id64 := int64(id) group.Status.GitlabId = &id64 group.Status.LastUpdatedTime = metav1.Time{ @@ -256,3 +318,11 @@ func (r *GroupReconciler) UpdateStatus(ctx context.Context, id int, group *gitla } return r.Status().Update(ctx, group) } + +// SetupWithManager sets up the controller with the Manager. +func (r *GroupReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&gitlabv1alpha1.Group{}). + Owns(&gitlabv1alpha1.Project{}). + Complete(r) +} \ No newline at end of file diff --git a/controllers/gitlab/group_controller_test.go b/controllers/gitlab/group_controller_test.go index 09e0424..74cbfad 100755 --- a/controllers/gitlab/group_controller_test.go +++ b/controllers/gitlab/group_controller_test.go @@ -88,146 +88,146 @@ Describe("group_controller", func() { Expect(err).ToNot(BeNil()) }) }) - When("getting the secret from the GitlabCredentials", func() { - Context("Secret doesn't exist", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - Url: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, + }) + When("getting the secret from the GitlabCredentials", func() { + Context("Secret doesn't exist", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + Url: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", }, - Status: gitlabv1alpha1.GroupStatus{}, - } + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - result, err := sut.Reconcile(contextMock, requestMock) - It("Should log an error regarding the missing credentials", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch secret from gitlab credentials")) - }) - It("Should return a requeue result and an error", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - Expect(err).ToNot(BeNil()) - }) + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + result, err := sut.Reconcile(contextMock, requestMock) + It("Should log an error regarding the missing credentials", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch secret from gitlab credentials")) + }) + It("Should return a requeue result and an error", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + Expect(err).ToNot(BeNil()) }) }) - When("Logging in to GitLab", func() { - Context("can't create client", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - Url: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return nil, &MockError{ - message: "mocked error", - } + }) + When("Logging in to GitLab", func() { + Context("can't create client", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + Url: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("Should log the failure", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to create gitlab client")) - }) - It("Should return an error", func() { - Expect(err).ToNot(BeNil()) - }) - It("should requeue the group", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return nil, &MockError{ + message: "mocked error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("Should log the failure", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to create gitlab client")) + }) + It("Should return an error", func() { + Expect(err).ToNot(BeNil()) + }) + It("should requeue the group", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) }) }) }) -- GitLab From b820c306c957f79188b230321048324304be7909 Mon Sep 17 00:00:00 2001 From: Jason van Brackel Date: Wed, 16 Jun 2021 10:02:06 -0400 Subject: [PATCH 08/15] fix: Address items from code review --- .../v1alpha1/gitlabcredentials_types.go | 7 +- controllers/gitlab/gitlab_client.go | 18 ++- controllers/gitlab/group_controller.go | 123 +++++++++--------- controllers/gitlab/group_controller_test.go | 82 +++++++----- controllers/gitlab/mocks_test.go | 14 +- 5 files changed, 141 insertions(+), 103 deletions(-) diff --git a/apis/gitlab/v1alpha1/gitlabcredentials_types.go b/apis/gitlab/v1alpha1/gitlabcredentials_types.go index 9414004..34c06b3 100644 --- a/apis/gitlab/v1alpha1/gitlabcredentials_types.go +++ b/apis/gitlab/v1alpha1/gitlabcredentials_types.go @@ -31,13 +31,16 @@ type GitlabCredentialsSpec struct { Username string `json:"username,omitempty"` //AccessToken is the SecretRef to the secret containing the Gitlab Access Token for the user. - AccessToken v1.SecretReference `json:"access_token,omitempty"` + AccessToken v1.SecretReference `json:"accessToken,omitempty"` + + //AccessTokenKey is the key of the secret data that contains the Gitlab Access Token for the user. + AccessTokenKey string `json:"accessTokenKey"` } // GitlabCredentialsStatus defines the observed state of GitlabCredentials type GitlabCredentialsStatus struct { // LastUsedTime is the time that this credential was last used to access a GitLab API - LastUsedDate metav1.Time `json:"last_used_date"` + LastUsedDate metav1.Time `json:"lastUsedDate"` } //+kubebuilder:object:root=true diff --git a/controllers/gitlab/gitlab_client.go b/controllers/gitlab/gitlab_client.go index ad13806..c15d515 100644 --- a/controllers/gitlab/gitlab_client.go +++ b/controllers/gitlab/gitlab_client.go @@ -1,10 +1,22 @@ package gitlab -import . "github.com/xanzy/go-gitlab" +import gitlab "github.com/xanzy/go-gitlab" type GitlabClient interface { - NewClient(token string, options ...ClientOptionFunc) (*Client, error) - ListGroups(opt *ListGroupsOptions, options ...RequestOptionFunc) ([]*Group, *Response, error) + NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) + ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) } +type GitlabClientImpl struct { + client *gitlab.Client +} + +func (c GitlabClientImpl) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + client, err := gitlab.NewClient(token, options...) + c.client = client + return client, err +} +func (c GitlabClientImpl) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { + return c.client.Groups.ListGroups(opt, options...) +} \ No newline at end of file diff --git a/controllers/gitlab/group_controller.go b/controllers/gitlab/group_controller.go index 37cdc6f..7a9e921 100644 --- a/controllers/gitlab/group_controller.go +++ b/controllers/gitlab/group_controller.go @@ -31,12 +31,32 @@ import ( gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" ) +// Errors +const ( + errorUnableToFetchGroup = "unable to fetch group" + errorUnableToFetchGitlabCredentials = "unable to fetch gitlab credentials" + errorUnableToFetchSecret = "unable to fetch secret from gitlab credentials" + errorUnableToCreateGitlabClient = "unable to create gitlab client" + errorWhileSearchingGroups = "Error while searching groups." + errorWhileCreatingGitlabGroup = "Error while creating GitLab group." + errorWhileUpdatingGitlabGroup = "Error while updating GitLab group." + errorUnableToUpdateStatus = "Unable to update status." + errorUnableToProcessProjects = "Unable to process projects" + errorTryingToCreateProject = "Error trying to create project" + errorLookingUpProject = "Error looking up project" + errorUpdatingProject = "Error updating project" +) + +// Group Status +const ( + CredentialNotFound = "CredentialNotFound" + CredentialSecretNotFound = "CredentialSecretNotFound" + GroupCreated = "Created" + GroupOK = "OK" +) +const Valkyrie = "Valkyrie" -const errorUnableToFetchGroup = "unable to fetch group" -const errorUnableToFetchGitlabCredentials = "unable to fetch gitlab credentials" -const errorUnableToFetchSecret = "unable to fetch secret from gitlab credentials" -const errorUnableToCreateGitlabClient = "unable to create gitlab client" // GroupReconciler reconciles the state of a Gitlab Group, for each reconciliation loop it will log in to Gitlab // with credentials in the secret provided. It will then check the state of the Group, and create it if necessary. @@ -82,7 +102,8 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl if err := r.Get(ctx, gitLabCredentialsName, &gitlabCredentials); err != nil { log.Error(err, errorUnableToFetchGitlabCredentials) - // TODO: (jvb) group.Status.State = "gitlab credential Not Found." + group.Status.State = CredentialNotFound + _ = r.updateStatus(ctx, 0, group) return ctrl.Result{Requeue: true}, err } @@ -96,50 +117,51 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl if err := r.Get(ctx, secretName, &secret); err != nil { log.Error(err, errorUnableToFetchSecret) - // TODO: (jvb) group.Status.State = "secret from gitlab credential not found." + group.Status.State = CredentialSecretNotFound + _ = r.updateStatus(ctx, 0, group) return ctrl.Result{Requeue: true}, err } // Login to Gitlab - //TODO Error for not found. - //TODO SecretKeyRef - var accessToken = string(secret.Data["accessToken"]) + var accessToken = string(secret.Data[gitlabCredentials.Spec.AccessTokenKey]) var gitlabClient *gitlab.Client var err error - if gitlabClient, err = gitlab.NewClient(accessToken, gitlab.WithBaseURL(gitlabCredentials.Spec.Url)); err != nil { + if gitlabClient, err = r.gitlabClient.NewClient(accessToken, gitlab.WithBaseURL(gitlabCredentials.Spec.Url)); err != nil { log.Error(err, errorUnableToCreateGitlabClient, "username", gitlabCredentials.Spec.Username, "url", gitlabCredentials.Spec.Url) return ctrl.Result{Requeue: true}, err } // See if the Group Exists var gitlabGroup *gitlab.Group - found, response, gitlabGroup, err := r.groupExists(gitlabClient, group) + found, _, gitlabGroup, err := r.groupExists(gitlabClient, group) if err != nil { - log.Error(err, "Error while searching groups.", "status", response.Status, "statusCode", response.StatusCode) + log.Error(err, errorWhileSearchingGroups, "status") return ctrl.Result{Requeue: true}, err } if !found { - if gitlabGroup, response, err = r.createGroup(gitlabClient, group); err != nil { - log.Error(err,"Error while creating GitLab group.", "group", group) + if gitlabGroup, _, err = r.createGroup(gitlabClient, group); err != nil { + + log.Error(err, errorWhileCreatingGitlabGroup, "group", group) return ctrl.Result{Requeue: true}, err } } else { - if gitlabGroup, response, err = r.updateGroup(gitlabClient, group); err != nil { - log.Error(err, "Error while updating GitLab group.", "group", group) + if gitlabGroup, _, err = r.updateGroup(gitlabClient, group); err != nil { + log.Error(err, errorWhileUpdatingGitlabGroup, "group", group) return ctrl.Result{Requeue: true}, err } } if err := r.updateStatus(ctx, gitlabGroup.ID, group); err != nil { - log.Error(err, "Unable to update status.", "group", group) + + log.Error(err, errorUnableToUpdateStatus, "group", group) return ctrl.Result{ Requeue: true }, err } if err := r.processProjects(ctx, group, !found, group.Spec.ProjectSpecs); err != nil { - log.Error(err, "Unable to process projects", "group", group) + log.Error(err, errorUnableToProcessProjects, "group", group) return ctrl.Result{Requeue: true}, err } @@ -158,7 +180,7 @@ func (r *GroupReconciler) groupExists(gitlabClient *gitlab.Client, group *gitlab return false, response, nil, err } - // TODO: (jvb) For work beyond the MVP we'll need to handle pagination. + // TODO: (jvb) For work beyond the MVP we'll need to handle pagination. May be superceeded by adam's client work. found := false for _, groupInList := range grouplist { found = group.Name == groupInList.Name @@ -175,13 +197,13 @@ func (r *GroupReconciler) createGroup(client *gitlab.Client, group *gitlabv1alph Description: &group.Spec.Description, } gitlabGroup, response, err := client.Groups.CreateGroup(&opt) - r.reconcileGroupMembers(client, gitlabGroup, group) group.Status.CreatedTime = metav1.Time{ Time: time.Now(), } group.ObjectMeta.Annotations["ID"] = fmt.Sprint(gitlabGroup.ID) - group.ObjectMeta.Annotations["Creator"] = "Valkyrie" // TODO Const - // TODO: (jvb) group.Status.State = "Created" // TODO Const + group.ObjectMeta.Annotations["Creator"] = Valkyrie + group.Status.State = GroupCreated + return gitlabGroup, response, err } @@ -191,7 +213,7 @@ func (r *GroupReconciler) updateGroup(client *gitlab.Client, group *gitlabv1alph Description: &group.Spec.Description, } gitlabGroup, response, err := client.Groups.UpdateGroup(group.Annotations["ID"], &opt) - r.reconcileGroupMembers(client, gitlabGroup, group) + group.Status.State = GroupOK group.Status.LastUpdatedTime = metav1.Time{ Time: time.Now(), } @@ -199,54 +221,23 @@ func (r *GroupReconciler) updateGroup(client *gitlab.Client, group *gitlabv1alph return gitlabGroup, response, err } -func (r *GroupReconciler) reconcileGroupMembers(client *gitlab.Client, gitlabGroup *gitlab.Group, group *gitlabv1alpha1.Group) error { - listGroupOptions := &gitlab.ListGroupMembersOptions{} - var groupMembers []*gitlab.GroupMember - var response *gitlab.Response - var err error - if groupMembers, response, err = client.Groups.ListGroupMembers(gitlabGroup.ID, listGroupOptions); err != nil { - r.Log.Error(err, "Error while listing group members.", "group", group, "response", response) - return err - } - found := false - var foundMember *gitlab.GroupMember - for _, groupMember := range groupMembers { - found = groupMember.Username == group.Spec.Owner - if found { - foundMember = groupMember - break - } - } - - if !found { - owner := gitlab.OwnerPermissions - addGroupMemberOptions := gitlab.AddGroupMemberOptions{ - UserID: &foundMember.ID, - AccessLevel: &owner, - } - - if _, response, err = client.GroupMembers.AddGroupMember(gitlabGroup.ID, &addGroupMemberOptions); err != nil { - r.Log.Error(err, "Error while listing group members.", "group", group, "response", response, "userId", foundMember.ID) - return err - } - } - return nil -} func (r *GroupReconciler) processProjects(ctx context.Context, group *gitlabv1alpha1.Group, newProject bool, specs []gitlabv1alpha1.ProjectSpec) error { for _, projectSpec := range specs { if newProject { if err := r.createProject(ctx, group, projectSpec); err != nil { - r.Log.Error(err, "Error trying to create project", "project", projectSpec) + + r.Log.Error(err, errorTryingToCreateProject, "project", projectSpec) return err } } else { if project, foundProject, err := r.getProject(ctx, group, projectSpec); err != nil { - r.Log.Error(err,"Error looking up project", "name", projectSpec.Name) + r.Log.Error(err, errorLookingUpProject, "name", projectSpec.Name) return err } else if foundProject { if _, err := r.updateProject(ctx,group, projectSpec, project); err != nil { - r.Log.Error(err, "Error updating project", "project", project) + + r.Log.Error(err, errorUpdatingProject, "project", project) } } } @@ -311,16 +302,22 @@ func (r *GroupReconciler) updateProject(ctx context.Context, group *gitlabv1alph } func (r *GroupReconciler) updateStatus(ctx context.Context, id int, group *gitlabv1alpha1.Group) error { - id64 := int64(id) - group.Status.GitlabId = &id64 - group.Status.LastUpdatedTime = metav1.Time{ - Time: time.Now(), + if id != 0 { + id64 := int64(id) + group.Status.GitlabId = &id64 + group.Status.LastUpdatedTime = metav1.Time{ + Time: time.Now(), + } } + return r.Status().Update(ctx, group) } // SetupWithManager sets up the controller with the Manager. func (r *GroupReconciler) SetupWithManager(mgr ctrl.Manager) error { + // Setup a default GitlabClient implementation + r.gitlabClient = &GitlabClientImpl{} + return ctrl.NewControllerManagedBy(mgr). For(&gitlabv1alpha1.Group{}). Owns(&gitlabv1alpha1.Project{}). diff --git a/controllers/gitlab/group_controller_test.go b/controllers/gitlab/group_controller_test.go index 74cbfad..f73bd07 100755 --- a/controllers/gitlab/group_controller_test.go +++ b/controllers/gitlab/group_controller_test.go @@ -15,38 +15,43 @@ import ( var _ = -Describe("group_controller", func() { +Describe("Reconcile", func() { builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) scheme, _ := builder.Build() - Describe("Reconcile", func() { - When("it looks up the API Object", func() { - Context("the API Object isn't found", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{} - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch group")) - }) - It("should return an empty result, and ignore the error.", func() { - Expect(result).Should(Equal(ctrl.Result{})) - Expect(err).Should(BeNil()) - }) + When("it looks up the API Object", func() { + Context("the API Object isn't found", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{} + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch group")) + }) + It("should return an empty result, and ignore the error.", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).Should(BeNil()) }) }) + }) +}) + +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() When("it looks up the GitlabCredentals", func() { Context("gitlab credentials are not found", func() { loggerMock := MockLogger{ @@ -89,6 +94,11 @@ Describe("group_controller", func() { }) }) }) + }) +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() When("getting the secret from the GitlabCredentials", func() { Context("Secret doesn't exist", func() { loggerMock := MockLogger{ @@ -150,9 +160,14 @@ Describe("group_controller", func() { }) }) }) - When("Logging in to GitLab", func() { + }) +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("logging in to GitLab", func() { Context("can't create client", func() { - loggerMock := MockLogger{ + loggerMock := &MockLogger{ WithValuesKeysAndValues: nil, WithValuesCalled: false, WithNameValue: "", @@ -163,7 +178,7 @@ Describe("group_controller", func() { requestMock := ctrl.Request{} sut := GroupReconciler{ Client: &clientMock, - Log: &loggerMock, + Log: loggerMock, Scheme: scheme, } requestMock.NamespacedName = types.NamespacedName{ @@ -221,7 +236,7 @@ Describe("group_controller", func() { result, err := sut.Reconcile(contextMock, requestMock) It("Should log the failure", func() { Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to create gitlab client")) + Expect(loggerMock.loggedMessage).To(Equal(errorUnableToCreateGitlabClient)) }) It("Should return an error", func() { Expect(err).ToNot(BeNil()) @@ -231,5 +246,4 @@ Describe("group_controller", func() { }) }) }) - }) -}) + }) \ No newline at end of file diff --git a/controllers/gitlab/mocks_test.go b/controllers/gitlab/mocks_test.go index b868aa2..61b4174 100755 --- a/controllers/gitlab/mocks_test.go +++ b/controllers/gitlab/mocks_test.go @@ -85,10 +85,22 @@ func (m *MockClient) DeleteAllOf(ctx context.Context, obj client.Object, opts .. panic("implement me") } -func (m *MockClient) Status() client.StatusWriter { +type MockStatusWriter struct { + +} + +func (m MockStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil +} + +func (m MockStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { panic("implement me") } +func (m *MockClient) Status() client.StatusWriter { + return MockStatusWriter{} +} + func (m *MockClient) Scheme() *runtime.Scheme { panic("implement me") } -- GitLab From a6173add0667d15a3e1e0da1a72f385ad22f7b76 Mon Sep 17 00:00:00 2001 From: Jason van Brackel Date: Wed, 16 Jun 2021 11:23:59 -0400 Subject: [PATCH 09/15] fix: lint issues --- .gitattributes | 1 + .../v1alpha1/gitlabcredentials_types.go | 138 ++-- .../v1alpha1/gitlabcredentials_types_test.go | 16 +- apis/gitlab/v1alpha1/group_types.go | 156 ++--- apis/gitlab/v1alpha1/group_types_test.go | 604 ++++++++-------- apis/gitlab/v1alpha1/project_types.go | 2 +- apis/gitlab/v1alpha1/zz_generated.deepcopy.go | 4 +- controllers/gitlab/gitlab_client.go | 12 +- .../gitlab/gitlabcredentials_controller.go | 8 +- .../gitlabcredentials_controller_test.go | 4 +- controllers/gitlab/group_controller.go | 648 +++++++++--------- controllers/gitlab/group_controller_test.go | 496 +++++++------- driver.go | 2 +- driver_test.go | 2 +- go.mod | 1 + go.sum | 3 + 16 files changed, 1053 insertions(+), 1044 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d01653a --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +text eol=lf diff --git a/apis/gitlab/v1alpha1/gitlabcredentials_types.go b/apis/gitlab/v1alpha1/gitlabcredentials_types.go index 4c29bba..5afef41 100644 --- a/apis/gitlab/v1alpha1/gitlabcredentials_types.go +++ b/apis/gitlab/v1alpha1/gitlabcredentials_types.go @@ -1,69 +1,69 @@ -/* -Copyright 2021. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha1 - -import ( - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// GitlabCredentialsSpec defines the desired state of GitlabCredentials, this stores a Gitlab username -// and Access Token for communicating with the Gitlab API. -type GitlabCredentialsSpec struct { - // URL is the url for the GitLab API that will be contacted. - Url string `json:"url,omitempty"` - - // Username is the Gitlab username for the account that will be communicating with the Gitlab API - Username string `json:"username,omitempty"` - - //AccessToken is the SecretRef to the secret containing the Gitlab Access Token for the user. - AccessToken v1.SecretReference `json:"accessToken,omitempty"` - - //AccessTokenKey is the key of the secret data that contains the Gitlab Access Token for the user. - AccessTokenKey string `json:"accessTokenKey"` -} - -// GitlabCredentialsStatus defines the observed state of GitlabCredentials -type GitlabCredentialsStatus struct { - // LastUsedTime is the time that this credential was last used to access a GitLab API - LastUsedDate metav1.Time `json:"lastUsedDate"` -} - -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status - -// GitlabCredentials is the Schema for the gitlabcredentials API -type GitlabCredentials struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec GitlabCredentialsSpec `json:"spec,omitempty"` - Status GitlabCredentialsStatus `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// GitlabCredentialsList contains a list of GitlabCredentials -type GitlabCredentialsList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []GitlabCredentials `json:"items"` -} - -func init() { - SchemeBuilder.Register(&GitlabCredentials{}, &GitlabCredentialsList{}) -} +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GitlabCredentialsSpec defines the desired state of GitlabCredentials, this stores a Gitlab username +// and Access Token for communicating with the Gitlab API. +type GitlabCredentialsSpec struct { + // URL is the url for the GitLab API that will be contacted. + URL string `json:"url,omitempty"` + + // Username is the Gitlab username for the account that will be communicating with the Gitlab API + Username string `json:"username,omitempty"` + + //AccessToken is the SecretRef to the secret containing the Gitlab Access Token for the user. + AccessToken v1.SecretReference `json:"accessToken,omitempty"` + + //AccessTokenKey is the key of the secret data that contains the Gitlab Access Token for the user. + AccessTokenKey string `json:"accessTokenKey"` +} + +// GitlabCredentialsStatus defines the observed state of GitlabCredentials +type GitlabCredentialsStatus struct { + // LastUsedTime is the time that this credential was last used to access a GitLab API + LastUsedDate metav1.Time `json:"lastUsedDate"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// GitlabCredentials is the Schema for the gitlabcredentials API +type GitlabCredentials struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GitlabCredentialsSpec `json:"spec,omitempty"` + Status GitlabCredentialsStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// GitlabCredentialsList contains a list of GitlabCredentials +type GitlabCredentialsList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []GitlabCredentials `json:"items"` +} + +func init() { + SchemeBuilder.Register(&GitlabCredentials{}, &GitlabCredentialsList{}) +} diff --git a/apis/gitlab/v1alpha1/gitlabcredentials_types_test.go b/apis/gitlab/v1alpha1/gitlabcredentials_types_test.go index 447b7e8..0fd49df 100755 --- a/apis/gitlab/v1alpha1/gitlabcredentials_types_test.go +++ b/apis/gitlab/v1alpha1/gitlabcredentials_types_test.go @@ -67,14 +67,14 @@ func initVarsGitlabCredentials() testVarsGitlabcredentials { testVars.objectList2 = GitlabCredentialsList{TypeMeta: objectList2Metatype, Items: objectItems2} testVars.testObjectSpec1 = GitlabCredentialsSpec{ - Url: "https://example1.com", + URL: "https://example1.com", Username: "", AccessToken: v1.SecretReference{ Name: "mySecret", Namespace: "aNamespace", }} testVars.testObjectSpec2 = GitlabCredentialsSpec{ - Url: "https://example.com", + URL: "https://example.com", Username: "user2", AccessToken: v1.SecretReference{ Name: "mySecret2", @@ -227,8 +227,8 @@ func TestDeepCopy_DeepCopyListObject_GitlabCredentials(t *testing.T) { t.Errorf("got %s want %s", got, want) } - var nil_test_ptr *GitlabCredentialsList = nil - var val = nil_test_ptr.DeepCopyObject() + var nilTestPtr *GitlabCredentialsList = nil + var val = nilTestPtr.DeepCopyObject() if val != nil { t.Errorf("got %s want %s", "not nil", "nil") } @@ -245,8 +245,8 @@ func TestDeepCopy_DeepCopySpec_GitlabCredentials(t *testing.T) { Expect(newObjectList).To(Equal(testVariables.testObjectSpec1)) }) - var nil_test_ptr *GitlabCredentialsSpec = nil - var val = nil_test_ptr.DeepCopy() + var nilTestPtr *GitlabCredentialsSpec = nil + var val = nilTestPtr.DeepCopy() if val != nil { t.Errorf("got %s want %s", "not nil", "nil") } @@ -273,8 +273,8 @@ func TestDeepCopy_DeepCopyStatus_GitlabCredentials(t *testing.T) { }) // a typed pointer set to nil - var nil_test_ptr *GitlabCredentialsStatus = nil - var val = nil_test_ptr.DeepCopy() + var nilTestPtr *GitlabCredentialsStatus = nil + var val = nilTestPtr.DeepCopy() if val != nil { t.Errorf("got %s want %s", "not nil", "nil") } diff --git a/apis/gitlab/v1alpha1/group_types.go b/apis/gitlab/v1alpha1/group_types.go index 1133af7..c2f1feb 100644 --- a/apis/gitlab/v1alpha1/group_types.go +++ b/apis/gitlab/v1alpha1/group_types.go @@ -1,78 +1,78 @@ -/* -Copyright 2021. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// GroupSpec defines the desired state of Group -type GroupSpec struct { - // Name is the name of the Group and will be used as part of the Url in Gitlab - // +kubebuilder:validation:required - Name string `json:"name"` - - // Description is the Gitlab Description for the group - // +kubebuilder:validation:Optional - Description string `json:"description"` - - // Owner is the Gitlab Username for the group owner - // +kubebuilder:validation:Optional - Owner string `json:"owner"` - - // GitlabCredentialsName is the name of the object in this namespace that contains authentication - // information for logging into the - GitlabCredentialsName string `json:"gitlabCredentialsName"` - - // ProjectSpecs are for the GitLab projects managed by this Group. It's expected that Projects - // will be managed at the group level rather than be adjusted themselves. - // +kubebuilder:validation:Optional - ProjectSpecs []ProjectSpec `json:"projects:omitempty"` -} - -// GroupStatus defines the observed state of Group -type GroupStatus struct { - GitlabId *int64 `json:"gitlabId"` - CreatedTime metav1.Time `json:"createdTime"` - LastUpdatedTime metav1.Time `json:"lastUpdatedTime"` - State string `json:"state"` -} - -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status - -// Group is the Schema for the groups API -type Group struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec GroupSpec `json:"spec,omitempty"` - Status GroupStatus `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// GroupList contains a list of Group -type GroupList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []Group `json:"items"` -} - -func init() { - SchemeBuilder.Register(&Group{}, &GroupList{}) -} +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GroupSpec defines the desired state of Group +type GroupSpec struct { + // Name is the name of the Group and will be used as part of the URL in Gitlab + // +kubebuilder:validation:required + Name string `json:"name"` + + // Description is the Gitlab Description for the group + // +kubebuilder:validation:Optional + Description string `json:"description"` + + // Owner is the Gitlab Username for the group owner + // +kubebuilder:validation:Optional + Owner string `json:"owner"` + + // GitlabCredentialsName is the name of the object in this namespace that contains authentication + // information for logging into the + GitlabCredentialsName string `json:"gitlabCredentialsName"` + + // ProjectSpecs are for the GitLab projects managed by this Group. It's expected that Projects + // will be managed at the group level rather than be adjusted themselves. + // +kubebuilder:validation:Optional + ProjectSpecs []ProjectSpec `json:"projects:omitempty"` +} + +// GroupStatus defines the observed state of Group +type GroupStatus struct { + GitlabID *int64 `json:"gitlabId"` + CreatedTime metav1.Time `json:"createdTime"` + LastUpdatedTime metav1.Time `json:"lastUpdatedTime"` + State string `json:"state"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// Group is the Schema for the groups API +type Group struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GroupSpec `json:"spec,omitempty"` + Status GroupStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// GroupList contains a list of Group +type GroupList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Group `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Group{}, &GroupList{}) +} diff --git a/apis/gitlab/v1alpha1/group_types_test.go b/apis/gitlab/v1alpha1/group_types_test.go index 952f2ee..03871c4 100644 --- a/apis/gitlab/v1alpha1/group_types_test.go +++ b/apis/gitlab/v1alpha1/group_types_test.go @@ -1,302 +1,302 @@ -package v1alpha1 - -import ( - "reflect" - "testing" - "time" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// Reusable test variables -type testVarsGroup = struct { - testKind string - testApiVersion string - testSpec string - testStatus string - - expectedKind string - expectedApiVersion string - expectedSpec string - expectedStatus string - - testObject1 Group - testObject2 Group - - objectItems1 []Group - objectList1 GroupList - - objectItems2 []Group - objectList2 GroupList - - testObjectSpec1 GroupSpec - testObjectSpec2 GroupSpec - - testObjectStatus1 GroupStatus - testObjectStatus2 GroupStatus -} - -func initVarsGroup() testVarsGroup { - testVars := testVarsGroup{} - - testVars.testKind = "TestKind" - testVars.testApiVersion = "v22" - testVars.testSpec = "test spec value" - testVars.testStatus = "test status value" - - testVars.expectedApiVersion = testVars.testApiVersion - testVars.expectedKind = testVars.testKind - testVars.expectedSpec = testVars.testSpec - testVars.expectedStatus = testVars.testStatus - - var object1MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: testVars.testKind, APIVersion: testVars.testApiVersion} - testVars.testObject1 = Group{TypeMeta: object1MetaType} - - var object2MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind2", APIVersion: "V99"} - testVars.testObject2 = Group{TypeMeta: object2MetaType} - - var objectList1MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind_List", APIVersion: "V12"} - var objectItems1 []Group = []Group{testVars.testObject1, testVars.testObject2} - // test_object_list = GroupList(object_list1_metaType,nil,object_items) - testVars.objectList1 = GroupList{TypeMeta: objectList1MetaType, Items: objectItems1} - - var objectList2Metatype metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind_List", APIVersion: "V12"} - var objectItems2 []Group = []Group{testVars.testObject2} - // test_object_list = GroupList(object_list1_metaType,nil,object_items) - testVars.objectList2 = GroupList{TypeMeta: objectList2Metatype, Items: objectItems2} - - testVars.testObjectSpec1 = GroupSpec{ - Name: "testGroup1", - GitlabCredentialsName: "nameOfTheCredentials", - Description: "testDescription1", - } - testVars.testObjectSpec2 = GroupSpec{ - Name: "testGroup2", - GitlabCredentialsName: "nameOfCredentials2", - ProjectSpecs: nil, - Description: "testDescription2", - } - - id1 := int64(1) - testVars.testObjectStatus1 = GroupStatus{ - GitlabId: &id1, - CreatedTime: metav1.Time{ - Time: time.Now(), - }, - LastUpdatedTime: metav1.Time{ - Time: time.Now(), - }, - } - - id2 := int64(2) - testVars.testObjectStatus2 = GroupStatus{ - GitlabId: &id2, - CreatedTime: metav1.Time{ - Time: time.Now(), - }, - LastUpdatedTime: metav1.Time{ - Time: time.Now(), - }, - } - - return testVars -} - -func TestGroupVars_Group(t *testing.T) { - - xType := reflect.TypeOf(GroupVersion) - // convert object type to string - got := xType.String() - want := "schema.GroupVersion" - - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} - -// Test Type called Group -func TestTypes_Group(t *testing.T) { - lTestVars := initVarsGroup() - want := lTestVars.expectedApiVersion - - got := lTestVars.testObject1.APIVersion - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} - -// DeepCopy -func TestDeepCopy_DeepCopy_Group(t *testing.T) { - lTestVars := initVarsGroup() - - newObject := lTestVars.testObject1.DeepCopy() - - // check api version - got := newObject.APIVersion - want := lTestVars.expectedApiVersion - - if got != want { - t.Errorf("got %s want %s", got, want) - } - - // check kind - got = newObject.Kind - want = lTestVars.expectedKind - - if got != want { - t.Errorf("got %s want %s", got, want) - } - - var nilTestPtr *Group = nil - var val = nilTestPtr.DeepCopyObject() - if val != nil { - t.Errorf("got %s want %s", "not nil", "nil") - } - - t.Log("Success") -} - -func TestDeepCopy_DeepCopyInto_Group(t *testing.T) { - lTestVars := initVarsGroup() - - lTestVars.testObject1.DeepCopyInto(&lTestVars.testObject2) - - got := lTestVars.testObject2.APIVersion - want := lTestVars.expectedApiVersion - - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} - -func TestDeepCopy_DeepCopyObject_Group(t *testing.T) { - lTestVars := initVarsGroup() - - newRuntimeObject := lTestVars.testObject1.DeepCopyObject() - newObject := newRuntimeObject.(*Group) - got := newObject.APIVersion - want := lTestVars.expectedApiVersion - - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} - -func TestDeepCopy_DeepCopyList_Group(t *testing.T) { - lTestVars := initVarsGroup() - newObjectList := lTestVars.objectList1.DeepCopy() - - got := newObjectList.Items[0].APIVersion - want := lTestVars.expectedApiVersion - - if got != want { - t.Errorf("got %s want %s", got, want) - } - - // a typed pointer set to nil - var nilTestPtr *GroupList = nil - var val = nilTestPtr.DeepCopy() - if val != nil { - t.Errorf("got %s want %s", "not nil", "nil") - } - t.Log("Success") -} - -func TestDeepCopy_DeepCopyIntoList_Group(t *testing.T) { - lTestVars := initVarsGroup() - - lTestVars.objectList1.DeepCopyInto(&lTestVars.objectList2) - - got := lTestVars.objectList2.Items[0].APIVersion - want := lTestVars.expectedApiVersion - - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} - -func TestDeepCopy_DeepCopyListObject_Group(t *testing.T) { - lTestVars := initVarsGroup() - - newRuntimeObject := lTestVars.objectList1.DeepCopyObject() - newObject := newRuntimeObject.(*GroupList) - got := newObject.Items[0].APIVersion - want := lTestVars.expectedApiVersion - - if got != want { - t.Errorf("got %s want %s", got, want) - } - - var nilTestPtr *GroupList = nil - var val = nilTestPtr.DeepCopyObject() - if val != nil { - t.Errorf("got %s want %s", "not nil", "nil") - } - - t.Log("Success") -} - -func TestDeepCopy_DeepCopySpec_Group(t *testing.T) { - lTestVars := initVarsGroup() - - newObjectList := lTestVars.testObjectSpec1.DeepCopy() - - It("Should DeepCopy the Spec", func() { - Expect(newObjectList).To(Equal(lTestVars.testObjectSpec1)) - }) - - var nilTestPtr *GroupSpec = nil - var val = nilTestPtr.DeepCopy() - if val != nil { - t.Errorf("got %s want %s", "not nil", "nil") - } - t.Log("Success") -} - -func TestDeepCopy_DeepCopySpecInto_Group(t *testing.T) { - lTestVars := initVarsGroup() - - lTestVars.testObjectSpec1.DeepCopyInto(&lTestVars.testObjectSpec2) - - It("Should DeepCopyInto a Group", func() { - Expect(lTestVars.testObjectSpec1).To(Equal(lTestVars.testObject2)) - }) -} - -func TestDeepCopy_DeepCopyStatus_Group(t *testing.T) { - lTestVars := initVarsGroup() - - newObjectStatus := lTestVars.testObjectStatus1.DeepCopy() - - It("Should DeepCopy the Status", func() { - Expect(newObjectStatus).To(Equal(lTestVars.testObjectStatus1)) - }) - - // a typed pointer set to nil - var nilTestPtr *GroupStatus = nil - var val = nilTestPtr.DeepCopy() - if val != nil { - t.Errorf("got %s want %s", "not nil", "nil") - } - - t.Log("Success") -} - -func TestDeepCopy_DeepCopyStatusInto_Group(t *testing.T) { - lTestVars := initVarsGroup() - - lTestVars.testObjectStatus1.DeepCopyInto(&lTestVars.testObjectStatus2) - - It("Should DeepCopyInto the Group", func() { - Expect(lTestVars.testObjectStatus2).To(Equal(lTestVars.testObjectSpec1)) - }) - - t.Log("Success") -} +package v1alpha1 + +import ( + "reflect" + "testing" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Reusable test variables +type testVarsGroup = struct { + testKind string + testAPIVersion string + testSpec string + testStatus string + + expectedKind string + expectedAPIVersion string + expectedSpec string + expectedStatus string + + testObject1 Group + testObject2 Group + + objectItems1 []Group + objectList1 GroupList + + objectItems2 []Group + objectList2 GroupList + + testObjectSpec1 GroupSpec + testObjectSpec2 GroupSpec + + testObjectStatus1 GroupStatus + testObjectStatus2 GroupStatus +} + +func initVarsGroup() testVarsGroup { + testVars := testVarsGroup{} + + testVars.testKind = "TestKind" + testVars.testAPIVersion = "v22" + testVars.testSpec = "test spec value" + testVars.testStatus = "test status value" + + testVars.expectedAPIVersion = testVars.testAPIVersion + testVars.expectedKind = testVars.testKind + testVars.expectedSpec = testVars.testSpec + testVars.expectedStatus = testVars.testStatus + + var object1MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: testVars.testKind, APIVersion: testVars.testAPIVersion} + testVars.testObject1 = Group{TypeMeta: object1MetaType} + + var object2MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind2", APIVersion: "V99"} + testVars.testObject2 = Group{TypeMeta: object2MetaType} + + var objectList1MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind_List", APIVersion: "V12"} + var objectItems1 []Group = []Group{testVars.testObject1, testVars.testObject2} + // test_object_list = GroupList(object_list1_metaType,nil,object_items) + testVars.objectList1 = GroupList{TypeMeta: objectList1MetaType, Items: objectItems1} + + var objectList2Metatype metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind_List", APIVersion: "V12"} + var objectItems2 []Group = []Group{testVars.testObject2} + // test_object_list = GroupList(object_list1_metaType,nil,object_items) + testVars.objectList2 = GroupList{TypeMeta: objectList2Metatype, Items: objectItems2} + + testVars.testObjectSpec1 = GroupSpec{ + Name: "testGroup1", + GitlabCredentialsName: "nameOfTheCredentials", + Description: "testDescription1", + } + testVars.testObjectSpec2 = GroupSpec{ + Name: "testGroup2", + GitlabCredentialsName: "nameOfCredentials2", + ProjectSpecs: nil, + Description: "testDescription2", + } + + id1 := int64(1) + testVars.testObjectStatus1 = GroupStatus{ + GitlabID: &id1, + CreatedTime: metav1.Time{ + Time: time.Now(), + }, + LastUpdatedTime: metav1.Time{ + Time: time.Now(), + }, + } + + id2 := int64(2) + testVars.testObjectStatus2 = GroupStatus{ + GitlabID: &id2, + CreatedTime: metav1.Time{ + Time: time.Now(), + }, + LastUpdatedTime: metav1.Time{ + Time: time.Now(), + }, + } + + return testVars +} + +func TestGroupVars_Group(t *testing.T) { + + xType := reflect.TypeOf(GroupVersion) + // convert object type to string + got := xType.String() + want := "schema.GroupVersion" + + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +// Test Type called Group +func TestTypes_Group(t *testing.T) { + lTestVars := initVarsGroup() + want := lTestVars.expectedAPIVersion + + got := lTestVars.testObject1.APIVersion + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +// DeepCopy +func TestDeepCopy_DeepCopy_Group(t *testing.T) { + lTestVars := initVarsGroup() + + newObject := lTestVars.testObject1.DeepCopy() + + // check API version + got := newObject.APIVersion + want := lTestVars.expectedAPIVersion + + if got != want { + t.Errorf("got %s want %s", got, want) + } + + // check kind + got = newObject.Kind + want = lTestVars.expectedKind + + if got != want { + t.Errorf("got %s want %s", got, want) + } + + var nilTestPtr *Group = nil + var val = nilTestPtr.DeepCopyObject() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + + t.Log("Success") +} + +func TestDeepCopy_DeepCopyInto_Group(t *testing.T) { + lTestVars := initVarsGroup() + + lTestVars.testObject1.DeepCopyInto(&lTestVars.testObject2) + + got := lTestVars.testObject2.APIVersion + want := lTestVars.expectedAPIVersion + + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +func TestDeepCopy_DeepCopyObject_Group(t *testing.T) { + lTestVars := initVarsGroup() + + newRuntimeObject := lTestVars.testObject1.DeepCopyObject() + newObject := newRuntimeObject.(*Group) + got := newObject.APIVersion + want := lTestVars.expectedAPIVersion + + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +func TestDeepCopy_DeepCopyList_Group(t *testing.T) { + lTestVars := initVarsGroup() + newObjectList := lTestVars.objectList1.DeepCopy() + + got := newObjectList.Items[0].APIVersion + want := lTestVars.expectedAPIVersion + + if got != want { + t.Errorf("got %s want %s", got, want) + } + + // a typed pointer set to nil + var nilTestPtr *GroupList = nil + var val = nilTestPtr.DeepCopy() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + t.Log("Success") +} + +func TestDeepCopy_DeepCopyIntoList_Group(t *testing.T) { + lTestVars := initVarsGroup() + + lTestVars.objectList1.DeepCopyInto(&lTestVars.objectList2) + + got := lTestVars.objectList2.Items[0].APIVersion + want := lTestVars.expectedAPIVersion + + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +func TestDeepCopy_DeepCopyListObject_Group(t *testing.T) { + lTestVars := initVarsGroup() + + newRuntimeObject := lTestVars.objectList1.DeepCopyObject() + newObject := newRuntimeObject.(*GroupList) + got := newObject.Items[0].APIVersion + want := lTestVars.expectedAPIVersion + + if got != want { + t.Errorf("got %s want %s", got, want) + } + + var nilTestPtr *GroupList = nil + var val = nilTestPtr.DeepCopyObject() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + + t.Log("Success") +} + +func TestDeepCopy_DeepCopySpec_Group(t *testing.T) { + lTestVars := initVarsGroup() + + newObjectList := lTestVars.testObjectSpec1.DeepCopy() + + It("Should DeepCopy the Spec", func() { + Expect(newObjectList).To(Equal(lTestVars.testObjectSpec1)) + }) + + var nilTestPtr *GroupSpec = nil + var val = nilTestPtr.DeepCopy() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + t.Log("Success") +} + +func TestDeepCopy_DeepCopySpecInto_Group(t *testing.T) { + lTestVars := initVarsGroup() + + lTestVars.testObjectSpec1.DeepCopyInto(&lTestVars.testObjectSpec2) + + It("Should DeepCopyInto a Group", func() { + Expect(lTestVars.testObjectSpec1).To(Equal(lTestVars.testObject2)) + }) +} + +func TestDeepCopy_DeepCopyStatus_Group(t *testing.T) { + lTestVars := initVarsGroup() + + newObjectStatus := lTestVars.testObjectStatus1.DeepCopy() + + It("Should DeepCopy the Status", func() { + Expect(newObjectStatus).To(Equal(lTestVars.testObjectStatus1)) + }) + + // a typed pointer set to nil + var nilTestPtr *GroupStatus = nil + var val = nilTestPtr.DeepCopy() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + + t.Log("Success") +} + +func TestDeepCopy_DeepCopyStatusInto_Group(t *testing.T) { + lTestVars := initVarsGroup() + + lTestVars.testObjectStatus1.DeepCopyInto(&lTestVars.testObjectStatus2) + + It("Should DeepCopyInto the Group", func() { + Expect(lTestVars.testObjectStatus2).To(Equal(lTestVars.testObjectSpec1)) + }) + + t.Log("Success") +} diff --git a/apis/gitlab/v1alpha1/project_types.go b/apis/gitlab/v1alpha1/project_types.go index 1a7c798..b61b3de 100644 --- a/apis/gitlab/v1alpha1/project_types.go +++ b/apis/gitlab/v1alpha1/project_types.go @@ -59,7 +59,7 @@ type ProjectSpec struct { // ProjectStatus defines the observed state of Project type ProjectStatus struct { - // Url is the url for the project in GitLab + // URL is the url for the project in GitLab URL string `json:"url"` // test Dummy is an example field of Project. Edit project_types.go to remove/update diff --git a/apis/gitlab/v1alpha1/zz_generated.deepcopy.go b/apis/gitlab/v1alpha1/zz_generated.deepcopy.go index dfa28ad..89108db 100644 --- a/apis/gitlab/v1alpha1/zz_generated.deepcopy.go +++ b/apis/gitlab/v1alpha1/zz_generated.deepcopy.go @@ -288,8 +288,8 @@ func (in *GroupSpec) DeepCopy() *GroupSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GroupStatus) DeepCopyInto(out *GroupStatus) { *out = *in - if in.GitlabId != nil { - in, out := &in.GitlabId, &out.GitlabId + if in.GitlabID != nil { + in, out := &in.GitlabID, &out.GitlabID *out = new(int64) **out = **in } diff --git a/controllers/gitlab/gitlab_client.go b/controllers/gitlab/gitlab_client.go index c15d515..bed2b43 100644 --- a/controllers/gitlab/gitlab_client.go +++ b/controllers/gitlab/gitlab_client.go @@ -2,21 +2,25 @@ package gitlab import gitlab "github.com/xanzy/go-gitlab" -type GitlabClient interface { +// Client is a facade for go-gitlab +type Client interface { NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) } -type GitlabClientImpl struct { +// ClientImpl is the Default implementation for the facade. +type ClientImpl struct { client *gitlab.Client } -func (c GitlabClientImpl) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { +// NewClient is a facade for the go-gitlab client NewClient call +func (c ClientImpl) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { client, err := gitlab.NewClient(token, options...) c.client = client return client, err } -func (c GitlabClientImpl) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { +// ListGroups is a facade for the go-gitlab client.Groups.ListGroups call. +func (c ClientImpl) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { return c.client.Groups.ListGroups(opt, options...) } \ No newline at end of file diff --git a/controllers/gitlab/gitlabcredentials_controller.go b/controllers/gitlab/gitlabcredentials_controller.go index 3ec1241..670200a 100644 --- a/controllers/gitlab/gitlabcredentials_controller.go +++ b/controllers/gitlab/gitlabcredentials_controller.go @@ -27,8 +27,8 @@ import ( gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" ) -// GitlabCredentialsReconciler reconciles a GitlabCredentials object -type GitlabCredentialsReconciler struct { +// CredentialsReconciler reconciles a GitlabCredentials object +type CredentialsReconciler struct { client.Client Log logr.Logger Scheme *runtime.Scheme @@ -41,7 +41,7 @@ type GitlabCredentialsReconciler struct { // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. We're leaving this empty for now as a // placeholder for future need. -func (r *GitlabCredentialsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +func (r *CredentialsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = r.Log.WithValues("gitlabcredentials", req.NamespacedName) // No logic here by intention. This is a placeholder for future logic. @@ -50,7 +50,7 @@ func (r *GitlabCredentialsReconciler) Reconcile(ctx context.Context, req ctrl.Re } // SetupWithManager sets up the controller with the Manager. -func (r *GitlabCredentialsReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *CredentialsReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&gitlabv1alpha1.GitlabCredentials{}). Complete(r) diff --git a/controllers/gitlab/gitlabcredentials_controller_test.go b/controllers/gitlab/gitlabcredentials_controller_test.go index 691897c..5bf034b 100755 --- a/controllers/gitlab/gitlabcredentials_controller_test.go +++ b/controllers/gitlab/gitlabcredentials_controller_test.go @@ -18,7 +18,7 @@ var _ = Describe("gitlabcredentials_controller", func() { WithNameValue: "", WithNameCalled: false, } - sut := GitlabCredentialsReconciler{ + sut := CredentialsReconciler{ Client: nil, Log: &logger, Scheme: nil, @@ -43,7 +43,7 @@ var _ = Describe("gitlabcredentials_controller", func() { }) Describe("SetupWithManager", func() { v1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.GitlabCredentials{}, &gitlabv1alpha1.GitlabCredentialsList{}) - sut := GitlabCredentialsReconciler{ + sut := CredentialsReconciler{ Client: nil, Log: nil, Scheme: nil, diff --git a/controllers/gitlab/group_controller.go b/controllers/gitlab/group_controller.go index 9f68bf1..dd26e43 100644 --- a/controllers/gitlab/group_controller.go +++ b/controllers/gitlab/group_controller.go @@ -1,325 +1,325 @@ -/* -Copyright 2021. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package gitlab - -import ( - "context" - "fmt" - "github.com/go-logr/logr" - "github.com/xanzy/go-gitlab" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "time" - gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" -) - -// Errors -const ( - errorUnableToFetchGroup = "unable to fetch group" - errorUnableToFetchGitlabCredentials = "unable to fetch gitlab credentials" - errorUnableToFetchSecret = "unable to fetch secret from gitlab credentials" - errorUnableToCreateGitlabClient = "unable to create gitlab client" - errorWhileSearchingGroups = "Error while searching groups." - errorWhileCreatingGitlabGroup = "Error while creating GitLab group." - errorWhileUpdatingGitlabGroup = "Error while updating GitLab group." - errorUnableToUpdateStatus = "Unable to update status." - errorUnableToProcessProjects = "Unable to process projects" - errorTryingToCreateProject = "Error trying to create project" - errorLookingUpProject = "Error looking up project" - errorUpdatingProject = "Error updating project" -) - -// Group Status -const ( - CredentialNotFound = "CredentialNotFound" - CredentialSecretNotFound = "CredentialSecretNotFound" - GroupCreated = "Created" - GroupOK = "OK" -) - -const Valkyrie = "Valkyrie" - - -// GroupReconciler reconciles the state of a Gitlab Group, for each reconciliation loop it will log in to Gitlab -// with credentials in the secret provided. It will then check the state of the Group, and create it if necessary. -type GroupReconciler struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - gitlabClient GitlabClient -} - -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups/finalizers,verbs=update -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=gitlabcredentials,verbs=get;list;watch -//+kubebuilder:rbac:groups=core,resources=secretreference,verbs=get;list;watch -//+kubebuilder:rbac:groups=core,resources=secret,verbs=get;list;watch -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=projects,verbs=get;list;watch;create;update;patch;delete - -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// The reconcile loop for the Gitlab Group will update the Group and any of the child Project API Objects. -func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := r.Log.WithValues("group", req.NamespacedName) - - group := &gitlabv1alpha1.Group{} - - // Get the Group Object - if err := r.Get(ctx, req.NamespacedName, group); err != nil { - log.Error(err, errorUnableToFetchGroup, "group", req.NamespacedName.Name) - // we'll ignore not-found errors, since they can't be fixed by an immediate - // requeue (we'll need to wait for a new notification), and we can get them - // on deleted requests. - return ctrl.Result{}, client.IgnoreNotFound(err) - } - - // Get the Gitlab Credentials - var gitLabCredentialsName = types.NamespacedName{ - Namespace: req.Namespace, - Name: group.Spec.GitlabCredentialsName, - } - - var gitlabCredentials gitlabv1alpha1.GitlabCredentials - if err := r.Get(ctx, gitLabCredentialsName, &gitlabCredentials); err != nil { - - log.Error(err, errorUnableToFetchGitlabCredentials) - group.Status.State = CredentialNotFound - _ = r.updateStatus(ctx, 0, group) - return ctrl.Result{Requeue: true}, err - } - - var secretName = types.NamespacedName{ - Namespace: gitlabCredentials.Spec.AccessToken.Namespace, - Name: gitlabCredentials.Spec.AccessToken.Name, - } - - var secret v1.Secret - // Get the Secret - if err := r.Get(ctx, secretName, &secret); err != nil { - - log.Error(err, errorUnableToFetchSecret) - group.Status.State = CredentialSecretNotFound - _ = r.updateStatus(ctx, 0, group) - - return ctrl.Result{Requeue: true}, err - } - - // Login to Gitlab - var accessToken = string(secret.Data[gitlabCredentials.Spec.AccessTokenKey]) - - var gitlabClient *gitlab.Client - var err error - if gitlabClient, err = r.gitlabClient.NewClient(accessToken, gitlab.WithBaseURL(gitlabCredentials.Spec.Url)); err != nil { - log.Error(err, errorUnableToCreateGitlabClient, "username", gitlabCredentials.Spec.Username, "url", gitlabCredentials.Spec.Url) - return ctrl.Result{Requeue: true}, err - } - - // See if the Group Exists - var gitlabGroup *gitlab.Group - found, _, gitlabGroup, err := r.groupExists(gitlabClient, group) - if err != nil { - log.Error(err, errorWhileSearchingGroups, "status") - return ctrl.Result{Requeue: true}, err - } - - if !found { - if gitlabGroup, _, err = r.createGroup(gitlabClient, group); err != nil { - - log.Error(err, errorWhileCreatingGitlabGroup, "group", group) - return ctrl.Result{Requeue: true}, err - } - } else { - if gitlabGroup, _, err = r.updateGroup(gitlabClient, group); err != nil { - log.Error(err, errorWhileUpdatingGitlabGroup, "group", group) - return ctrl.Result{Requeue: true}, err - } - } - - if err := r.updateStatus(ctx, gitlabGroup.ID, group); err != nil { - - log.Error(err, errorUnableToUpdateStatus, "group", group) - return ctrl.Result{ Requeue: true }, err - } - - if err := r.processProjects(ctx, group, !found, group.Spec.ProjectSpecs); err != nil { - log.Error(err, errorUnableToProcessProjects, "group", group) - return ctrl.Result{Requeue: true}, err - } - - return ctrl.Result{}, nil -} - -func (r *GroupReconciler) groupExists(gitlabClient *gitlab.Client, group *gitlabv1alpha1.Group) (bool, *gitlab.Response, *gitlab.Group, error) { - - var grouplist []*gitlab.Group - var response *gitlab.Response - var err error - if grouplist, response, err = gitlabClient.Groups.ListGroups( - &gitlab.ListGroupsOptions{ - Search: &group.Spec.Name, - }); err != nil { - return false, response, nil, err - } - - // TODO: (jvb) For work beyond the MVP we'll need to handle pagination. May be superceeded by adam's client work. - found := false - for _, groupInList := range grouplist { - found = group.Name == groupInList.Name - if found { - return found, response, groupInList, nil - } - } - return found, response, nil, nil -} - -func (r *GroupReconciler) createGroup(client *gitlab.Client, group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { - opt := gitlab.CreateGroupOptions{ - Name: &group.Spec.Name, - Description: &group.Spec.Description, - } - gitlabGroup, response, err := client.Groups.CreateGroup(&opt) - group.Status.CreatedTime = metav1.Time{ - Time: time.Now(), - } - group.ObjectMeta.Annotations["ID"] = fmt.Sprint(gitlabGroup.ID) - group.ObjectMeta.Annotations["Creator"] = Valkyrie - group.Status.State = GroupCreated - - return gitlabGroup, response, err -} - -func (r *GroupReconciler) updateGroup(client *gitlab.Client, group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { - opt := gitlab.UpdateGroupOptions{ - Name: &group.Spec.Name, - Description: &group.Spec.Description, - } - gitlabGroup, response, err := client.Groups.UpdateGroup(group.Annotations["ID"], &opt) - group.Status.State = GroupOK - group.Status.LastUpdatedTime = metav1.Time{ - Time: time.Now(), - } - - return gitlabGroup, response, err -} - - -func (r *GroupReconciler) processProjects(ctx context.Context, group *gitlabv1alpha1.Group, newProject bool, specs []gitlabv1alpha1.ProjectSpec) error { - for _, projectSpec := range specs { - if newProject { - if err := r.createProject(ctx, group, projectSpec); err != nil { - - r.Log.Error(err, errorTryingToCreateProject, "project", projectSpec) - return err - } - } else { - if project, foundProject, err := r.getProject(ctx, group, projectSpec); err != nil { - r.Log.Error(err, errorLookingUpProject, "name", projectSpec.Name) - return err - } else if foundProject { - if _, err := r.updateProject(ctx,group, projectSpec, project); err != nil { - - r.Log.Error(err, errorUpdatingProject, "project", project) - } - } - } - } - return nil -} - -func (r *GroupReconciler) getProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec) (*gitlabv1alpha1.Project, bool, error) { - var project gitlabv1alpha1.Project - projectName := types.NamespacedName{ - Namespace: group.Namespace, - Name: fmt.Sprintf("%s-%s", group.Name, spec.Name), - } - err := r.Get(ctx, projectName, &project) - - projectNotFound := client.IgnoreNotFound(err) - - if projectNotFound == nil { - return nil, false, nil - } - - return &project, true, err -} - -func (r *GroupReconciler) createProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec) error { - blockOwnerDeletion := true - managingController := true - return r.Create(ctx, &gitlabv1alpha1.Project{ - ObjectMeta: ctrl.ObjectMeta{ - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: group.APIVersion, - Kind: group.Kind, - Name: group.Name, - UID: group.UID, - Controller: &managingController, - BlockOwnerDeletion: &blockOwnerDeletion, - }, - }, - }, - Spec: gitlabv1alpha1.ProjectSpec{ - Name: spec.Name, - Path: spec.Path, - ImpactLevel: spec.ImpactLevel, - StorageTypes: spec.StorageTypes, - VirtualService: spec.VirtualService, - ServicePort: spec.ServicePort, - Language: spec.Language, - }, - }) -} - -func (r *GroupReconciler) updateProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec, project *gitlabv1alpha1.Project) (*gitlabv1alpha1.Project, error) { - project.Spec.Name = spec.Name - project.Spec.Path = spec.Path - project.Spec.ImpactLevel = spec.ImpactLevel - project.Spec.StorageTypes = spec.StorageTypes - project.Spec.VirtualService = spec.VirtualService - project.Spec.ServicePort = spec.ServicePort - project.Spec.Language = spec.Language - return project, r.Update(ctx, project) -} - -func (r *GroupReconciler) updateStatus(ctx context.Context, id int, group *gitlabv1alpha1.Group) error { - if id != 0 { - id64 := int64(id) - group.Status.GitlabId = &id64 - group.Status.LastUpdatedTime = metav1.Time{ - Time: time.Now(), - } - } - - return r.Status().Update(ctx, group) -} - -// SetupWithManager sets up the controller with the Manager. -func (r *GroupReconciler) SetupWithManager(mgr ctrl.Manager) error { - // Setup a default GitlabClient implementation - r.gitlabClient = &GitlabClientImpl{} - - return ctrl.NewControllerManagedBy(mgr). - For(&gitlabv1alpha1.Group{}). - Owns(&gitlabv1alpha1.Project{}). - Complete(r) +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gitlab + +import ( + "context" + "fmt" + "github.com/go-logr/logr" + "github.com/xanzy/go-gitlab" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "time" + gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" +) + +// Errors +const ( + errorUnableToFetchGroup = "unable to fetch group" + errorUnableToFetchGitlabCredentials = "unable to fetch gitlab credentials" + errorUnableToFetchSecret = "unable to fetch secret from gitlab credentials" + errorUnableToCreateGitlabClient = "unable to create gitlab client" + errorWhileSearchingGroups = "Error while searching groups." + errorWhileCreatingGitlabGroup = "Error while creating GitLab group." + errorWhileUpdatingGitlabGroup = "Error while updating GitLab group." + errorUnableToUpdateStatus = "Unable to update status." + errorUnableToProcessProjects = "Unable to process projects" + errorTryingToCreateProject = "Error trying to create project" + errorLookingUpProject = "Error looking up project" + errorUpdatingProject = "Error updating project" +) + +// Group Status +const ( + CredentialNotFound = "CredentialNotFound" + CredentialSecretNotFound = "CredentialSecretNotFound" + GroupCreated = "Created" + GroupOK = "OK" +) + +const valkyrie = "valkyrie" + + +// GroupReconciler reconciles the state of a Gitlab Group, for each reconciliation loop it will log in to Gitlab +// with credentials in the secret provided. It will then check the state of the Group, and create it if necessary. +type GroupReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + gitlabClient Client +} + +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups/finalizers,verbs=update +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=gitlabcredentials,verbs=get;list;watch +//+kubebuilder:rbac:groups=core,resources=secretreference,verbs=get;list;watch +//+kubebuilder:rbac:groups=core,resources=secret,verbs=get;list;watch +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=projects,verbs=get;list;watch;create;update;patch;delete + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// The reconcile loop for the Gitlab Group will update the Group and any of the child Project API Objects. +func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := r.Log.WithValues("group", req.NamespacedName) + + group := &gitlabv1alpha1.Group{} + + // Get the Group Object + if err := r.Get(ctx, req.NamespacedName, group); err != nil { + log.Error(err, errorUnableToFetchGroup, "group", req.NamespacedName.Name) + // we'll ignore not-found errors, since they can't be fixed by an immediate + // requeue (we'll need to wait for a new notification), and we can get them + // on deleted requests. + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + // Get the Gitlab Credentials + var gitLabCredentialsName = types.NamespacedName{ + Namespace: req.Namespace, + Name: group.Spec.GitlabCredentialsName, + } + + var gitlabCredentials gitlabv1alpha1.GitlabCredentials + if err := r.Get(ctx, gitLabCredentialsName, &gitlabCredentials); err != nil { + + log.Error(err, errorUnableToFetchGitlabCredentials) + group.Status.State = CredentialNotFound + _ = r.updateStatus(ctx, 0, group) + return ctrl.Result{Requeue: true}, err + } + + var secretName = types.NamespacedName{ + Namespace: gitlabCredentials.Spec.AccessToken.Namespace, + Name: gitlabCredentials.Spec.AccessToken.Name, + } + + var secret v1.Secret + // Get the Secret + if err := r.Get(ctx, secretName, &secret); err != nil { + + log.Error(err, errorUnableToFetchSecret) + group.Status.State = CredentialSecretNotFound + _ = r.updateStatus(ctx, 0, group) + + return ctrl.Result{Requeue: true}, err + } + + // Login to Gitlab + var accessToken = string(secret.Data[gitlabCredentials.Spec.AccessTokenKey]) + + var gitlabClient *gitlab.Client + var err error + if gitlabClient, err = r.gitlabClient.NewClient(accessToken, gitlab.WithBaseURL(gitlabCredentials.Spec.URL)); err != nil { + log.Error(err, errorUnableToCreateGitlabClient, "username", gitlabCredentials.Spec.Username, "url", gitlabCredentials.Spec.URL) + return ctrl.Result{Requeue: true}, err + } + + // See if the Group Exists + var gitlabGroup *gitlab.Group + found, _, gitlabGroup, err := r.groupExists(gitlabClient, group) + if err != nil { + log.Error(err, errorWhileSearchingGroups, "status") + return ctrl.Result{Requeue: true}, err + } + + if !found { + if gitlabGroup, _, err = r.createGroup(gitlabClient, group); err != nil { + + log.Error(err, errorWhileCreatingGitlabGroup, "group", group) + return ctrl.Result{Requeue: true}, err + } + } else { + if gitlabGroup, _, err = r.updateGroup(gitlabClient, group); err != nil { + log.Error(err, errorWhileUpdatingGitlabGroup, "group", group) + return ctrl.Result{Requeue: true}, err + } + } + + if err := r.updateStatus(ctx, gitlabGroup.ID, group); err != nil { + + log.Error(err, errorUnableToUpdateStatus, "group", group) + return ctrl.Result{ Requeue: true }, err + } + + if err := r.processProjects(ctx, group, !found, group.Spec.ProjectSpecs); err != nil { + log.Error(err, errorUnableToProcessProjects, "group", group) + return ctrl.Result{Requeue: true}, err + } + + return ctrl.Result{}, nil +} + +func (r *GroupReconciler) groupExists(gitlabClient *gitlab.Client, group *gitlabv1alpha1.Group) (bool, *gitlab.Response, *gitlab.Group, error) { + + var grouplist []*gitlab.Group + var response *gitlab.Response + var err error + if grouplist, response, err = gitlabClient.Groups.ListGroups( + &gitlab.ListGroupsOptions{ + Search: &group.Spec.Name, + }); err != nil { + return false, response, nil, err + } + + // TODO: (jvb) For work beyond the MVP we'll need to handle pagination. May be superceeded by adam's client work. + found := false + for _, groupInList := range grouplist { + found = group.Name == groupInList.Name + if found { + return found, response, groupInList, nil + } + } + return found, response, nil, nil +} + +func (r *GroupReconciler) createGroup(client *gitlab.Client, group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { + opt := gitlab.CreateGroupOptions{ + Name: &group.Spec.Name, + Description: &group.Spec.Description, + } + gitlabGroup, response, err := client.Groups.CreateGroup(&opt) + group.Status.CreatedTime = metav1.Time{ + Time: time.Now(), + } + group.ObjectMeta.Annotations["ID"] = fmt.Sprint(gitlabGroup.ID) + group.ObjectMeta.Annotations["Creator"] = valkyrie + group.Status.State = GroupCreated + + return gitlabGroup, response, err +} + +func (r *GroupReconciler) updateGroup(client *gitlab.Client, group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { + opt := gitlab.UpdateGroupOptions{ + Name: &group.Spec.Name, + Description: &group.Spec.Description, + } + gitlabGroup, response, err := client.Groups.UpdateGroup(group.Annotations["ID"], &opt) + group.Status.State = GroupOK + group.Status.LastUpdatedTime = metav1.Time{ + Time: time.Now(), + } + + return gitlabGroup, response, err +} + + +func (r *GroupReconciler) processProjects(ctx context.Context, group *gitlabv1alpha1.Group, newProject bool, specs []gitlabv1alpha1.ProjectSpec) error { + for _, projectSpec := range specs { + if newProject { + if err := r.createProject(ctx, group, projectSpec); err != nil { + + r.Log.Error(err, errorTryingToCreateProject, "project", projectSpec) + return err + } + } else { + if project, foundProject, err := r.getProject(ctx, group, projectSpec); err != nil { + r.Log.Error(err, errorLookingUpProject, "name", projectSpec.Name) + return err + } else if foundProject { + if _, err := r.updateProject(ctx,group, projectSpec, project); err != nil { + + r.Log.Error(err, errorUpdatingProject, "project", project) + } + } + } + } + return nil +} + +func (r *GroupReconciler) getProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec) (*gitlabv1alpha1.Project, bool, error) { + var project gitlabv1alpha1.Project + projectName := types.NamespacedName{ + Namespace: group.Namespace, + Name: fmt.Sprintf("%s-%s", group.Name, spec.Name), + } + err := r.Get(ctx, projectName, &project) + + projectNotFound := client.IgnoreNotFound(err) + + if projectNotFound == nil { + return nil, false, nil + } + + return &project, true, err +} + +func (r *GroupReconciler) createProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec) error { + blockOwnerDeletion := true + managingController := true + return r.Create(ctx, &gitlabv1alpha1.Project{ + ObjectMeta: ctrl.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: group.APIVersion, + Kind: group.Kind, + Name: group.Name, + UID: group.UID, + Controller: &managingController, + BlockOwnerDeletion: &blockOwnerDeletion, + }, + }, + }, + Spec: gitlabv1alpha1.ProjectSpec{ + Name: spec.Name, + Path: spec.Path, + ImpactLevel: spec.ImpactLevel, + StorageTypes: spec.StorageTypes, + VirtualService: spec.VirtualService, + ServicePort: spec.ServicePort, + Language: spec.Language, + }, + }) +} + +func (r *GroupReconciler) updateProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec, project *gitlabv1alpha1.Project) (*gitlabv1alpha1.Project, error) { + project.Spec.Name = spec.Name + project.Spec.Path = spec.Path + project.Spec.ImpactLevel = spec.ImpactLevel + project.Spec.StorageTypes = spec.StorageTypes + project.Spec.VirtualService = spec.VirtualService + project.Spec.ServicePort = spec.ServicePort + project.Spec.Language = spec.Language + return project, r.Update(ctx, project) +} + +func (r *GroupReconciler) updateStatus(ctx context.Context, id int, group *gitlabv1alpha1.Group) error { + if id != 0 { + id64 := int64(id) + group.Status.GitlabID = &id64 + group.Status.LastUpdatedTime = metav1.Time{ + Time: time.Now(), + } + } + + return r.Status().Update(ctx, group) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *GroupReconciler) SetupWithManager(mgr ctrl.Manager) error { + // Setup a default GitlabClient implementation + r.gitlabClient = &ClientImpl{} + + return ctrl.NewControllerManagedBy(mgr). + For(&gitlabv1alpha1.Group{}). + Owns(&gitlabv1alpha1.Project{}). + Complete(r) } \ No newline at end of file diff --git a/controllers/gitlab/group_controller_test.go b/controllers/gitlab/group_controller_test.go index 481d84e..86c076b 100755 --- a/controllers/gitlab/group_controller_test.go +++ b/controllers/gitlab/group_controller_test.go @@ -1,249 +1,249 @@ -package gitlab - -import ( - "context" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/xanzy/go-gitlab" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" -) - - -var _ = -Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("it looks up the API Object", func() { - Context("the API Object isn't found", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{} - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch group")) - }) - It("should return an empty result, and ignore the error.", func() { - Expect(result).Should(Equal(ctrl.Result{})) - Expect(err).Should(BeNil()) - }) - }) - }) -}) - -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("it looks up the GitlabCredentals", func() { - Context("gitlab credentials are not found", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - clientMock.GetFunction = nil - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{}, - Status: gitlabv1alpha1.GroupStatus{}, - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - result, err := sut.Reconcile(contextMock, requestMock) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch gitlab credentials")) - }) - It("should return a reconcile result, and the error.", func() { - Expect(result).To(Equal(ctrl.Result{ - Requeue: true, - })) - Expect(err).ToNot(BeNil()) - }) - }) - }) - }) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("getting the secret from the GitlabCredentials", func() { - Context("Secret doesn't exist", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - Url: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - result, err := sut.Reconcile(contextMock, requestMock) - It("Should log an error regarding the missing credentials", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch secret from gitlab credentials")) - }) - It("Should return a requeue result and an error", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - Expect(err).ToNot(BeNil()) - }) - }) - }) - }) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("logging in to GitLab", func() { - Context("can't create client", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - Url: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return nil, &MockError{ - message: "mocked error", - } - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("Should log the failure", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorUnableToCreateGitlabClient)) - }) - It("Should return an error", func() { - Expect(err).ToNot(BeNil()) - }) - It("should requeue the group", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - }) - }) +package gitlab + +import ( + "context" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/xanzy/go-gitlab" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" +) + + +var _ = +Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("it looks up the API Object", func() { + Context("the API Object isn't found", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{} + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch group")) + }) + It("should return an empty result, and ignore the error.", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).Should(BeNil()) + }) + }) + }) +}) + +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("it looks up the GitlabCredentals", func() { + Context("gitlab credentials are not found", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + clientMock.GetFunction = nil + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{}, + Status: gitlabv1alpha1.GroupStatus{}, + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + result, err := sut.Reconcile(contextMock, requestMock) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch gitlab credentials")) + }) + It("should return a reconcile result, and the error.", func() { + Expect(result).To(Equal(ctrl.Result{ + Requeue: true, + })) + Expect(err).ToNot(BeNil()) + }) + }) + }) + }) +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("getting the secret from the GitlabCredentials", func() { + Context("Secret doesn't exist", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + result, err := sut.Reconcile(contextMock, requestMock) + It("Should log an error regarding the missing credentials", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch secret from gitlab credentials")) + }) + It("Should return a requeue result and an error", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + Expect(err).ToNot(BeNil()) + }) + }) + }) + }) +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("logging in to GitLab", func() { + Context("can't create client", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return nil, &MockError{ + message: "mocked error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("Should log the failure", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorUnableToCreateGitlabClient)) + }) + It("Should return an error", func() { + Expect(err).ToNot(BeNil()) + }) + It("should requeue the group", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + }) + }) }) \ No newline at end of file diff --git a/driver.go b/driver.go index 29d95a4..3702e18 100755 --- a/driver.go +++ b/driver.go @@ -177,7 +177,7 @@ func (d driverImpl) instantiateControllers(mgr manager.Manager) []controllers.Ma Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }, - &gitlabcontrollers.GitlabCredentialsReconciler{ + &gitlabcontrollers.CredentialsReconciler{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("gitlab").WithName("GitlabCredentials"), Scheme: mgr.GetScheme(), diff --git a/driver_test.go b/driver_test.go index 3704f4e..8d29e3d 100755 --- a/driver_test.go +++ b/driver_test.go @@ -55,7 +55,7 @@ var _ = Describe("instantiateControllers", func() { Expect(controllers[11]).To(BeAssignableToTypeOf(&customer.ChiefInformationSecurityOfficerReconciler{})) }) It("Should create a GitlabCredentials Controller", func() { - Expect(controllers[12]).To(BeAssignableToTypeOf(&gitlab.GitlabCredentialsReconciler{})) + Expect(controllers[12]).To(BeAssignableToTypeOf(&gitlab.CredentialsReconciler{})) }) }) }) diff --git a/go.mod b/go.mod index bfcfe41..57c0834 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/onsi/gomega v1.13.0 github.com/xanzy/go-gitlab v0.50.0 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect + golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect k8s.io/api v0.19.2 k8s.io/apimachinery v0.19.2 k8s.io/client-go v0.19.2 diff --git a/go.sum b/go.sum index e7e213c..47ea8a2 100644 --- a/go.sum +++ b/go.sum @@ -439,6 +439,8 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -563,6 +565,7 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -- GitLab From 697fa7760a0606cab8e63d7c528eb3fd5f8e2900 Mon Sep 17 00:00:00 2001 From: Jason van Brackel Date: Thu, 17 Jun 2021 12:33:17 -0400 Subject: [PATCH 10/15] fix: add test coverage for CTF and fix bugs --- controllers/gitlab/gitlab_client.go | 32 +- controllers/gitlab/group_controller.go | 85 +- controllers/gitlab/group_controller_test.go | 919 +++++++++++++++++++- controllers/gitlab/mocks_test.go | 821 ++++++++--------- 4 files changed, 1406 insertions(+), 451 deletions(-) diff --git a/controllers/gitlab/gitlab_client.go b/controllers/gitlab/gitlab_client.go index bed2b43..1e8b3dd 100644 --- a/controllers/gitlab/gitlab_client.go +++ b/controllers/gitlab/gitlab_client.go @@ -6,6 +6,10 @@ import gitlab "github.com/xanzy/go-gitlab" type Client interface { NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) + ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) + CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) + UpdateGroup(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) + } // ClientImpl is the Default implementation for the facade. @@ -23,4 +27,30 @@ func (c ClientImpl) NewClient(token string, options ...gitlab.ClientOptionFunc) // ListGroups is a facade for the go-gitlab client.Groups.ListGroups call. func (c ClientImpl) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { return c.client.Groups.ListGroups(opt, options...) -} \ No newline at end of file +} + +// ListGroupsByName is a facade for the go-gitlab client.Groups.ListGroups call, with the group name as a search option. +func (c ClientImpl) ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return c.client.Groups.ListGroups( + &gitlab.ListGroupsOptions{ + Search: &name, + }) +} + +// CreateGroup is a facade for the go-gitlab client.Groups.CreateGroup +func (c ClientImpl) CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) { + opt := gitlab.CreateGroupOptions{ + Name: &name, + Description: &description, + } + return c.client.Groups.CreateGroup(&opt) +} + +// UpdateGroup is a facade for the go-gitlab client.Group.UpdateGroup +func (c ClientImpl) UpdateGroup(id string, name string, descrption string) (*gitlab.Group, *gitlab.Response, error) { + opt := gitlab.UpdateGroupOptions{ + Name: &name, + Description: &descrption, + } + return c.client.Groups.UpdateGroup(id, &opt) +} diff --git a/controllers/gitlab/group_controller.go b/controllers/gitlab/group_controller.go index dd26e43..1917714 100644 --- a/controllers/gitlab/group_controller.go +++ b/controllers/gitlab/group_controller.go @@ -126,41 +126,39 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl // Login to Gitlab var accessToken = string(secret.Data[gitlabCredentials.Spec.AccessTokenKey]) - var gitlabClient *gitlab.Client var err error - if gitlabClient, err = r.gitlabClient.NewClient(accessToken, gitlab.WithBaseURL(gitlabCredentials.Spec.URL)); err != nil { + if _, err = r.gitlabClient.NewClient(accessToken, gitlab.WithBaseURL(gitlabCredentials.Spec.URL)); err != nil { log.Error(err, errorUnableToCreateGitlabClient, "username", gitlabCredentials.Spec.Username, "url", gitlabCredentials.Spec.URL) return ctrl.Result{Requeue: true}, err } // See if the Group Exists var gitlabGroup *gitlab.Group - found, _, gitlabGroup, err := r.groupExists(gitlabClient, group) + found, _, gitlabGroup, err := r.groupExists(group) if err != nil { log.Error(err, errorWhileSearchingGroups, "status") return ctrl.Result{Requeue: true}, err } if !found { - if gitlabGroup, _, err = r.createGroup(gitlabClient, group); err != nil { + if gitlabGroup, _, err = r.createGroup(group); err != nil { log.Error(err, errorWhileCreatingGitlabGroup, "group", group) return ctrl.Result{Requeue: true}, err } } else { - if gitlabGroup, _, err = r.updateGroup(gitlabClient, group); err != nil { + if gitlabGroup, _, err = r.updateGroup(group); err != nil { log.Error(err, errorWhileUpdatingGitlabGroup, "group", group) return ctrl.Result{Requeue: true}, err } } if err := r.updateStatus(ctx, gitlabGroup.ID, group); err != nil { - log.Error(err, errorUnableToUpdateStatus, "group", group) return ctrl.Result{ Requeue: true }, err } - if err := r.processProjects(ctx, group, !found, group.Spec.ProjectSpecs); err != nil { + if err := r.processProjects(ctx, group, group.Spec.ProjectSpecs); err != nil { log.Error(err, errorUnableToProcessProjects, "group", group) return ctrl.Result{Requeue: true}, err } @@ -168,22 +166,19 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl return ctrl.Result{}, nil } -func (r *GroupReconciler) groupExists(gitlabClient *gitlab.Client, group *gitlabv1alpha1.Group) (bool, *gitlab.Response, *gitlab.Group, error) { +func (r *GroupReconciler) groupExists(group *gitlabv1alpha1.Group) (bool, *gitlab.Response, *gitlab.Group, error) { var grouplist []*gitlab.Group var response *gitlab.Response var err error - if grouplist, response, err = gitlabClient.Groups.ListGroups( - &gitlab.ListGroupsOptions{ - Search: &group.Spec.Name, - }); err != nil { + if grouplist, response, err = r.gitlabClient.ListGroupsByName(group.Spec.Name); err != nil { return false, response, nil, err } // TODO: (jvb) For work beyond the MVP we'll need to handle pagination. May be superceeded by adam's client work. found := false for _, groupInList := range grouplist { - found = group.Name == groupInList.Name + found = group.Spec.Name == groupInList.Name if found { return found, response, groupInList, nil } @@ -191,54 +186,50 @@ func (r *GroupReconciler) groupExists(gitlabClient *gitlab.Client, group *gitlab return found, response, nil, nil } -func (r *GroupReconciler) createGroup(client *gitlab.Client, group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { - opt := gitlab.CreateGroupOptions{ - Name: &group.Spec.Name, - Description: &group.Spec.Description, - } - gitlabGroup, response, err := client.Groups.CreateGroup(&opt) - group.Status.CreatedTime = metav1.Time{ - Time: time.Now(), +func (r *GroupReconciler) createGroup(group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { + gitlabGroup, response, err := r.gitlabClient.CreateGroup(group.Spec.Name, group.Spec.Description) + + if err == nil { + group.Status.CreatedTime = metav1.Time{ + Time: time.Now(), + } + group.ObjectMeta.Annotations["ID"] = fmt.Sprint(gitlabGroup.ID) + group.ObjectMeta.Annotations["Creator"] = valkyrie + group.Status.State = GroupCreated } - group.ObjectMeta.Annotations["ID"] = fmt.Sprint(gitlabGroup.ID) - group.ObjectMeta.Annotations["Creator"] = valkyrie - group.Status.State = GroupCreated return gitlabGroup, response, err } -func (r *GroupReconciler) updateGroup(client *gitlab.Client, group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { - opt := gitlab.UpdateGroupOptions{ - Name: &group.Spec.Name, - Description: &group.Spec.Description, - } - gitlabGroup, response, err := client.Groups.UpdateGroup(group.Annotations["ID"], &opt) - group.Status.State = GroupOK - group.Status.LastUpdatedTime = metav1.Time{ - Time: time.Now(), +func (r *GroupReconciler) updateGroup(group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { + gitlabGroup, response, err := r.gitlabClient.UpdateGroup(group.ObjectMeta.Annotations["ID"], group.Spec.Name, group.Spec.Description) + + if err == nil { + group.Status.State = GroupOK + group.Status.LastUpdatedTime = metav1.Time{ + Time: time.Now(), + } } return gitlabGroup, response, err } -func (r *GroupReconciler) processProjects(ctx context.Context, group *gitlabv1alpha1.Group, newProject bool, specs []gitlabv1alpha1.ProjectSpec) error { +func (r *GroupReconciler) processProjects(ctx context.Context, group *gitlabv1alpha1.Group, specs []gitlabv1alpha1.ProjectSpec) error { for _, projectSpec := range specs { - if newProject { - if err := r.createProject(ctx, group, projectSpec); err != nil { - - r.Log.Error(err, errorTryingToCreateProject, "project", projectSpec) - return err + if project, foundProject, err := r.getProject(ctx, group, projectSpec); err != nil { + r.Log.Error(err, errorLookingUpProject, "name", projectSpec.Name) + return err + } else if foundProject { + if _, err := r.updateProject(ctx, projectSpec, project); err != nil { + + r.Log.Error(err, errorUpdatingProject, "project", project) } } else { - if project, foundProject, err := r.getProject(ctx, group, projectSpec); err != nil { - r.Log.Error(err, errorLookingUpProject, "name", projectSpec.Name) + if err := r.createProject(ctx, group, projectSpec); err != nil { + + r.Log.Error(err, errorTryingToCreateProject, "project", projectSpec) return err - } else if foundProject { - if _, err := r.updateProject(ctx,group, projectSpec, project); err != nil { - - r.Log.Error(err, errorUpdatingProject, "project", project) - } } } } @@ -290,7 +281,7 @@ func (r *GroupReconciler) createProject(ctx context.Context, group *gitlabv1alph }) } -func (r *GroupReconciler) updateProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec, project *gitlabv1alpha1.Project) (*gitlabv1alpha1.Project, error) { +func (r *GroupReconciler) updateProject(ctx context.Context, spec gitlabv1alpha1.ProjectSpec, project *gitlabv1alpha1.Project) (*gitlabv1alpha1.Project, error) { project.Spec.Name = spec.Name project.Spec.Path = spec.Path project.Spec.ImpactLevel = spec.ImpactLevel diff --git a/controllers/gitlab/group_controller_test.go b/controllers/gitlab/group_controller_test.go index 86c076b..c404b27 100755 --- a/controllers/gitlab/group_controller_test.go +++ b/controllers/gitlab/group_controller_test.go @@ -8,6 +8,7 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "net/http" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" @@ -15,7 +16,7 @@ import ( var _ = -Describe("Reconcile", func() { + Describe("Reconcile", func() { builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) scheme, _ := builder.Build() When("it looks up the API Object", func() { @@ -27,7 +28,9 @@ Describe("Reconcile", func() { WithNameCalled: false, } contextMock := context.TODO() - clientMock := MockClient{} + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) requestMock := ctrl.Request{} sut := GroupReconciler{ @@ -47,7 +50,6 @@ Describe("Reconcile", func() { }) }) }) - var _ = Describe("Reconcile", func() { builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) @@ -61,7 +63,9 @@ var _ = WithNameCalled: false, } contextMock := context.TODO() - clientMock := MockClient{} + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} requestMock := ctrl.Request{} sut := GroupReconciler{ Client: &clientMock, @@ -108,7 +112,9 @@ var _ = WithNameCalled: false, } contextMock := context.TODO() - clientMock := MockClient{} + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} requestMock := ctrl.Request{} sut := GroupReconciler{ Client: &clientMock, @@ -174,7 +180,9 @@ var _ = WithNameCalled: false, } contextMock := context.TODO() - clientMock := MockClient{} + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} requestMock := ctrl.Request{} sut := GroupReconciler{ Client: &clientMock, @@ -246,4 +254,903 @@ var _ = }) }) }) + }) + +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("groupExists fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, &MockError{ + message: "Error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorWhileSearchingGroups)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) + }) + +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("createGroup fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{ &gitlab.Group{ + ID: 0, + Name: "", + Path: "", + Description: "", + MembershipLock: false, + Visibility: "", + LFSEnabled: false, + AvatarURL: "", + WebURL: "", + RequestAccessEnabled: false, + FullName: "", + FullPath: "", + ParentID: 0, + Projects: nil, + Statistics: nil, + CustomAttributes: nil, + ShareWithGroupLock: false, + RequireTwoFactorAuth: false, + TwoFactorGracePeriod: 0, + ProjectCreationLevel: "", + AutoDevopsEnabled: false, + SubGroupCreationLevel: "", + EmailsDisabled: false, + MentionsDisabled: false, + RunnersToken: "", + SharedProjects: nil, + SharedWithGroups: nil, + LDAPCN: "", + LDAPAccess: 0, + LDAPGroupLinks: nil, + SharedRunnersMinutesLimit: 0, + ExtraSharedRunnersMinutesLimit: 0, + MarkedForDeletionOn: nil, + CreatedAt: nil, + } } + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + createGroupFunction: func(name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, &MockError{ + message: "Error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorWhileCreatingGitlabGroup)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) + }) +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("updateGroup fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{ {Name: "agroup"} } + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, &MockError{ + message: "Error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorWhileUpdatingGitlabGroup)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) + }) + +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("updateStatus fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ + statusWriter: &MockStatusWriter{ + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return &MockError{ "error"} + }, + }, + } + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{ {Name: "agroup"} } + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return &gitlab.Group{ Name: "agroup" }, &gitlab.Response{}, nil + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorUnableToUpdateStatus)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) + }) + +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("processProjects fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ + statusWriter: &MockStatusWriter{ + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }, + }, + createFunction: func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error{ + return &MockError{message: "error"} + }, + } + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: []gitlabv1alpha1.ProjectSpec{ { Name: "ProjectSpec"} }, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{ {Name: "agroup"} } + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return &gitlab.Group{ Name: "agroup" }, &gitlab.Response{}, nil + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorUnableToProcessProjects)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) + }) + +var _ = + Describe("groupExists", func() { + When("the gitlab client fails", func() { + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, &MockError{ + message: "mockedError", + } + }, + }, + } + found, response, groups, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "An error"}}) + It("should return that it didn't find the group", func() { + Expect(found).To(Equal(false)) + }) + It("should return the gitlab response", func() { + Expect(response).ToNot(BeNil()) + }) + It("should return a nil group", func() { + Expect(groups).To(BeNil()) + }) + It("should return an error", func() { + Expect(err).ToNot(BeNil()) + }) + }) + When("the group is in the list", func() { + group := &gitlab.Group{ Name: "A group" } + returnGroups := []*gitlab.Group{ group } + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return returnGroups, &gitlab.Response{}, nil + }, + }, + } + + found, response, returnedGroup, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "A group"}}) + It("should return that it found the group", func() { + Expect(found).To(Equal(true)) + }) + It("should return the gitlab response", func() { + Expect(response).ToNot(BeNil()) + }) + It("should return list with the group", func() { + Expect(returnedGroup).To(Equal(group)) + }) + It("should not return an error", func() { + Expect(err).To(BeNil()) + }) + }) + When("the group is not in the list", func() { + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, nil + }, + }, + } + + found, response, groups, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "A group"}}) + It("should return that it did not find the group", func() { + Expect(found).To(Equal(false)) + }) + It("should return the gitlab response", func() { + Expect(response).ToNot(BeNil()) + }) + It("should return nil for the group list", func() { + Expect(groups).To(BeNil()) + }) + It("should not return an error", func() { + Expect(err).To(BeNil()) + }) + }) + }) +var _ = + Describe("createGroup", func() { + expectedGroup := &gitlab.Group{ ID: 1, Name: "A test group"} + expectedResponse := &gitlab.Response{ + Response: &http.Response{}, + TotalItems: 1, + TotalPages: 1, + ItemsPerPage: 10, + CurrentPage: 1, + NextPage: 0, + PreviousPage: 0, + } + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + createGroupFunction: func(name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return expectedGroup, expectedResponse, nil + }, + }, + } + group, response, err := sut.createGroup( + &gitlabv1alpha1.Group{ + ObjectMeta: ctrl.ObjectMeta{Annotations: make(map[string]string)}, + Spec: gitlabv1alpha1.GroupSpec{Name: "a group"}, + }) + It("should return the gitlab group created", func() { + Expect(group).To(Equal(expectedGroup)) + }) + It("should return the gitlab response", func(){ + Expect(response).To(Equal(expectedResponse)) + }) + It("should return the error created by the call, or nil", func() { + Expect(err).To(BeNil()) + }) + }) + +var _ = + Describe("updateGroup", func() { + expectedGroup := &gitlab.Group{ID: 2, Name: "A test group"} + expectedResponse := &gitlab.Response{ + Response: &http.Response{}, + TotalItems: 1, + TotalPages: 1, + ItemsPerPage: 10, + CurrentPage: 1, + NextPage: 0, + PreviousPage: 0, + } + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return expectedGroup, expectedResponse, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return []*gitlab.Group{{Name: "a group"}}, &gitlab.Response{}, nil + }, + }, + } + group, response, err := sut.updateGroup( + &gitlabv1alpha1.Group{ + ObjectMeta: ctrl.ObjectMeta{Annotations: make(map[string]string)}, + Spec: gitlabv1alpha1.GroupSpec{Name: "a group"}, + Status: gitlabv1alpha1.GroupStatus{ + GitlabID: nil, + CreatedTime: metav1.Time{}, + LastUpdatedTime: metav1.Time{}, + State: "", + }, + }) + It("should return the gitlab group created", func() { + Expect(group).To(Equal(expectedGroup)) + }) + It("should return the gitlab response", func(){ + Expect(response).To(Equal(expectedResponse)) + }) + It("should return the error created by the call, or nil", func() { + Expect(err).To(BeNil()) + }) + }) + +var _ = + Describe("SetupWithManager", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + err := sut.SetupWithManager(&MockManager{ + Log: &loggerMock, + builder: builder, + }) + It("should setup the default gitlab client", func() { + Expect(sut.gitlabClient).To(BeAssignableToTypeOf(&ClientImpl{})) + }) + It("should return no error", func() { + Expect(err).To(BeNil()) + }) + }) + +var _ = Describe("updateStatus", func() { + When("group id is not 0", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + group := gitlabv1alpha1.Group{Status: gitlabv1alpha1.GroupStatus{ + GitlabID: nil, + CreatedTime: metav1.Time{}, + LastUpdatedTime: metav1.Time{}, + State: "", + }} + sut.updateStatus(&MockContext{}, 1, &group) + + It("should update the gitlab id", func() { + Expect(*group.Status.GitlabID).To(Equal(int64(1))) + }) + It("should update the last updated time", func() { + Expect(group.Status.LastUpdatedTime.Time).To(Not(BeNil())) + }) + }) +}) + +var _ = + Describe("updateProject", func() { + spec := gitlabv1alpha1.ProjectSpec{ + Name: "a project", + Path: "https://example.com.path", + ImpactLevel: "2", + StorageTypes: nil, + VirtualService: "default", + ServicePort: 8081, + Language: "golang", + } + project := gitlabv1alpha1.Project{Spec: gitlabv1alpha1.ProjectSpec{}} + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + mockError := &MockError{"true"} + clientMock := MockClient{ + statusWriter: MockStatusWriter{ + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }, + }, + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return mockError + }, + } + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + + returnedProject, err := sut.updateProject(context.TODO(), spec, &project) + It("should update the project from the project specification", func() { + Expect(project.Spec.Name).To(Equal(spec.Name)) + Expect(project.Spec.ServicePort).To(Equal(spec.ServicePort)) + Expect(project.Spec.Language).To(Equal(spec.Language)) + Expect(project.Spec.Path).To(Equal(spec.Path)) + Expect(project.Spec.VirtualService).To(Equal(spec.VirtualService)) + Expect(project.Spec.StorageTypes).To(Equal(spec.StorageTypes)) + Expect(project.Spec.ImpactLevel).To(Equal(spec.ImpactLevel)) + }) + It("should call the kubernetes client update function", func() { + Expect(clientMock.updateFunctionCalled).To(BeTrue()) + }) + It("should return the result of the update function", func() { + Expect(err).To(Equal(mockError)) + }) + It("should return the updated project", func() { + Expect(project).To(Equal(*returnedProject)) + }) }) \ No newline at end of file diff --git a/controllers/gitlab/mocks_test.go b/controllers/gitlab/mocks_test.go index 40340bb..d96b11d 100755 --- a/controllers/gitlab/mocks_test.go +++ b/controllers/gitlab/mocks_test.go @@ -1,397 +1,424 @@ -package gitlab - -import ( - "context" - "github.com/go-logr/logr" - "github.com/jinzhu/copier" - "github.com/xanzy/go-gitlab" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/record" - "net/http" - "sigs.k8s.io/controller-runtime/pkg/cache" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/healthz" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/scheme" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "time" - "valkyrie.dso.mil/valkyrie-api/controllers" -) - -type MockClient struct { - GetFunction func(ctx context.Context, key client.ObjectKey, obj client.Object) error - GetCalled bool - expectedObjects map[client.ObjectKey]client.Object - NotFoundError error -} - -func (m *MockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { - m.GetCalled = true - if m.GetFunction != nil { - return m.GetFunction(ctx, key, obj) - } - - if m.expectedObjects == nil { - return nil - } - - if m.expectedObjects[key] == nil { - return &errors.StatusError{ - ErrStatus: metav1.Status{ - TypeMeta: metav1.TypeMeta{}, - ListMeta: metav1.ListMeta{}, - Status: string(metav1.StatusReasonNotFound), - Message: "NotFound", - Reason: metav1.StatusReasonNotFound, - Details: nil, - Code: 0, - }, - } - } - - foundObject := m.expectedObjects[key] - - copier.Copy(obj, foundObject) - - return nil -} - -func (m *MockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { - panic("implement me") -} - -func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { - panic("implement me") -} - -func (m *MockClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { - panic("implement me") -} - -func (m *MockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - panic("implement me") -} - -func (m *MockClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { - panic("implement me") -} - -func (m *MockClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { - panic("implement me") -} - -type MockStatusWriter struct { - -} - -func (m MockStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil -} - -func (m MockStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { - panic("implement me") -} - -func (m *MockClient) Status() client.StatusWriter { - return MockStatusWriter{} -} - -func (m *MockClient) Scheme() *runtime.Scheme { - panic("implement me") -} - -func (m *MockClient) RESTMapper() meta.RESTMapper { - panic("implement me") -} - -type MockContext struct { -} - -func (m *MockContext) Deadline() (deadline time.Time, ok bool) { - panic("implement me") -} - -func (m *MockContext) Done() <-chan struct{} { - panic("implement me") -} - -func (m *MockContext) Err() error { - panic("implement me") -} - -func (m *MockContext) Value(key interface{}) interface{} { - panic("implement me") -} - -type MockLogger struct { - WithValuesKeysAndValues []interface{} - WithValuesCalled bool - WithNameValue string - WithNameCalled bool - loggedMessage string - keysAndValues []interface{} - logFunction func(msg string, keysAndValues ...interface{}) - logLevelCalled string - level int -} - -func (m *MockLogger) Enabled() bool { - return true -} - -func (m *MockLogger) Info(msg string, keysAndValues ...interface{}) { - m.loggedMessage = msg - m.keysAndValues = keysAndValues - m.logLevelCalled = "Info" - if m.logFunction != nil { - m.logFunction(msg, keysAndValues) - return - } -} - -func (m *MockLogger) Error(err error, msg string, keysAndValues ...interface{}) { - m.loggedMessage = msg - m.keysAndValues = keysAndValues - m.logLevelCalled = "Error" - if m.logFunction != nil { - m.logFunction(msg, keysAndValues) - return - } -} - -func (m *MockLogger) V(level int) logr.Logger { - m.level = level - return m -} - -func (m *MockLogger) WithValues(keysAndValues ...interface{}) logr.Logger { - m.WithValuesCalled = true - for i := 0; i < len(keysAndValues); i++ { - m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues[i]) - } - m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues) - - return m -} - -func (m *MockLogger) WithName(name string) logr.Logger { - m.WithNameCalled = true - m.WithNameValue = name - return m -} - -type MockController struct { - setupWithManagerWasCalled bool -} - -func (m *MockController) SetupWithManager(manager manager.Manager) error { - m.setupWithManagerWasCalled = true - return nil -} - -type MockManager struct { - Log logr.Logger - addHealthzCheckFunction func(name string, check healthz.Checker) error - addHealthzCheckWasCalled bool - addReadyzCheckFunction func(name string, check healthz.Checker) error - addReadyzCheckWasCalled bool - startFunction func(ctx context.Context) error - startWasCalled bool - builder *scheme.Builder - Fields []interface{} - runnable manager.Runnable -} - -func (m *MockManager) Add(runnable manager.Runnable) error { - m.runnable = runnable - - return nil -} - -func (m *MockManager) Elected() <-chan struct{} { - panic("implement me") -} - -func (m *MockManager) SetFields(i interface{}) error { - if m.Fields == nil { - m.Fields = make([]interface{}, 0) - } - m.Fields = append(m.Fields, i) - - return nil -} - -func (m *MockManager) AddMetricsExtraHandler(path string, handler http.Handler) error { - panic("implement me") -} - -func (m *MockManager) AddHealthzCheck(name string, check healthz.Checker) error { - m.addHealthzCheckWasCalled = true - if m.addHealthzCheckFunction == nil { - return nil - } - return m.addHealthzCheckFunction(name, check) -} - -func (m *MockManager) AddReadyzCheck(name string, check healthz.Checker) error { - m.addReadyzCheckWasCalled = true - if m.addReadyzCheckFunction == nil { - return nil - } - - return m.addReadyzCheckFunction(name, check) -} - -func (m *MockManager) Start(ctx context.Context) error { - m.startWasCalled = true - if m.startFunction == nil { - return nil - } - return m.startFunction(ctx) -} - -func (m *MockManager) GetConfig() *rest.Config { - return &rest.Config{} -} - -func (m *MockManager) GetScheme() *runtime.Scheme { - scheme, _ := m.builder.Build() - return scheme -} - -func (m *MockManager) GetClient() client.Client { - return nil -} - -func (m *MockManager) GetFieldIndexer() client.FieldIndexer { - panic("implement me") -} - -func (m *MockManager) GetCache() cache.Cache { - panic("implement me") -} - -func (m *MockManager) GetEventRecorderFor(name string) record.EventRecorder { - panic("implement me") -} - -func (m *MockManager) GetRESTMapper() meta.RESTMapper { - panic("implement me") -} - -func (m *MockManager) GetAPIReader() client.Reader { - panic("implement me") -} - -func (m *MockManager) GetWebhookServer() *webhook.Server { - panic("implement me") -} - -func (m *MockManager) GetLogger() logr.Logger { - return m.Log -} - -type MockRunFunctionParameters struct { - options zap.Options - metricsAddress string - healthProbeAddress string - enableLeaderElection bool -} - -type MockDriver struct { - parseCommandLineCalled bool - parseCommandLineFunction func() (string, bool, string, zap.Options, error) - runCalled bool - runFunction func(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) - runFunctionParameters MockRunFunctionParameters - newManagerCalled bool - newManagerFunction func(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) - instantiateControllersCalled bool - instantiateControllersFunction func(mgr manager.Manager) []controllers.ManagedController - setupControllerWithManagerCalled bool - setupControllersFunction func(controller controllers.ManagedController, manager manager.Manager) error -} - -func (m *MockDriver) parseCommandLine() (string, bool, string, zap.Options, error) { - m.parseCommandLineCalled = true - if m.parseCommandLineFunction == nil { - return "", true, "", zap.Options{}, nil - } - - return m.parseCommandLineFunction() -} - -func (m *MockDriver) run(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) { - m.runCalled = true - m.runFunctionParameters = MockRunFunctionParameters{ - options: opts, - metricsAddress: metricsAddress, - healthProbeAddress: healthProbeAddress, - enableLeaderElection: enableLeaderElection, - } - if m.runFunction == nil { - return 0, nil - } - - return m.runFunction(opts, metricsAddress, healthProbeAddress, enableLeaderElection) -} - -func (m *MockDriver) newManager(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) { - m.newManagerCalled = true - if m.newManagerFunction == nil { - return &MockManager{}, nil - } - - return m.newManagerFunction(metricsAddress, healthProbeAddress, enableLeaderElection) -} - -func (m *MockDriver) instantiateControllers(mgr manager.Manager) []controllers.ManagedController { - m.instantiateControllersCalled = true - - if m.instantiateControllersFunction == nil { - return []controllers.ManagedController{ - &MockController{}, - } - } - - return m.instantiateControllersFunction(mgr) -} - -func (m *MockDriver) setupControllerWithManager(controller controllers.ManagedController, manager manager.Manager) error { - m.setupControllerWithManagerCalled = true - - if m.setupControllersFunction == nil { - return nil - } - - return m.setupControllersFunction(controller, manager) -} - -type MockError struct { - message string -} - -func (m *MockError) Error() string { - if m.message == "" { - m.message = "mock Error" - } - return m.message -} - -type MockGitlabClient struct { - newClientFunction func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) - listGroupsFunction func(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) -} - -func (m *MockGitlabClient) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return m.newClientFunction(token, options...) -} - -func (m *MockGitlabClient) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { - return m.listGroupsFunction(opt, options...) -} +package gitlab + +import ( + "context" + "github.com/go-logr/logr" + "github.com/jinzhu/copier" + "github.com/xanzy/go-gitlab" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + "net/http" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/scheme" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "time" + "valkyrie.dso.mil/valkyrie-api/controllers" +) + +type MockClient struct { + GetFunction func(ctx context.Context, key client.ObjectKey, obj client.Object) error + GetCalled bool + expectedObjects map[client.ObjectKey]client.Object + NotFoundError error + statusWriter client.StatusWriter + createFunction func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error + updateFunction func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error + updateFunctionCalled bool +} + +func (m *MockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { + m.GetCalled = true + if m.GetFunction != nil { + return m.GetFunction(ctx, key, obj) + } + + if m.expectedObjects == nil { + return nil + } + + if m.expectedObjects[key] == nil { + return &errors.StatusError{ + ErrStatus: metav1.Status{ + TypeMeta: metav1.TypeMeta{}, + ListMeta: metav1.ListMeta{}, + Status: string(metav1.StatusReasonNotFound), + Message: "NotFound", + Reason: metav1.StatusReasonNotFound, + Details: nil, + Code: 0, + }, + } + } + + foundObject := m.expectedObjects[key] + + copier.Copy(obj, foundObject) + + return nil +} + +func (m *MockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + panic("implement me") +} + +func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + if m.createFunction == nil { + return nil + } + return m.createFunction(ctx, obj, opts...) +} + +func (m *MockClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { + panic("implement me") +} + +func (m *MockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + m.updateFunctionCalled = true + if m.updateFunction == nil { + return nil + } + return m.updateFunction(ctx, obj, opts...) +} + +func (m *MockClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + panic("implement me") +} + +func (m *MockClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { + panic("implement me") +} + +type MockStatusWriter struct { + updateFunction func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error +} + +func (m MockStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return m.updateFunction(ctx, obj, opts...) +} + +func (m MockStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + panic("implement me") +} + +func (m *MockClient) Status() client.StatusWriter { + return m.statusWriter +} + +func (m *MockClient) Scheme() *runtime.Scheme { + panic("implement me") +} + +func (m *MockClient) RESTMapper() meta.RESTMapper { + panic("implement me") +} + +type MockContext struct { +} + +func (m *MockContext) Deadline() (deadline time.Time, ok bool) { + panic("implement me") +} + +func (m *MockContext) Done() <-chan struct{} { + panic("implement me") +} + +func (m *MockContext) Err() error { + panic("implement me") +} + +func (m *MockContext) Value(key interface{}) interface{} { + panic("implement me") +} + +type MockLogger struct { + WithValuesKeysAndValues []interface{} + WithValuesCalled bool + WithNameValue string + WithNameCalled bool + loggedMessage string + keysAndValues []interface{} + logFunction func(msg string, keysAndValues ...interface{}) + logLevelCalled string + level int +} + +func (m *MockLogger) Enabled() bool { + return true +} + +func (m *MockLogger) Info(msg string, keysAndValues ...interface{}) { + m.loggedMessage = msg + m.keysAndValues = keysAndValues + m.logLevelCalled = "Info" + if m.logFunction != nil { + m.logFunction(msg, keysAndValues) + return + } +} + +func (m *MockLogger) Error(err error, msg string, keysAndValues ...interface{}) { + m.loggedMessage = msg + m.keysAndValues = keysAndValues + m.logLevelCalled = "Error" + if m.logFunction != nil { + m.logFunction(msg, keysAndValues) + return + } +} + +func (m *MockLogger) V(level int) logr.Logger { + m.level = level + return m +} + +func (m *MockLogger) WithValues(keysAndValues ...interface{}) logr.Logger { + m.WithValuesCalled = true + for i := 0; i < len(keysAndValues); i++ { + m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues[i]) + } + m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues) + + return m +} + +func (m *MockLogger) WithName(name string) logr.Logger { + m.WithNameCalled = true + m.WithNameValue = name + return m +} + +type MockController struct { + setupWithManagerWasCalled bool +} + +func (m *MockController) SetupWithManager(manager manager.Manager) error { + m.setupWithManagerWasCalled = true + return nil +} + +type MockManager struct { + Log logr.Logger + addHealthzCheckFunction func(name string, check healthz.Checker) error + addHealthzCheckWasCalled bool + addReadyzCheckFunction func(name string, check healthz.Checker) error + addReadyzCheckWasCalled bool + startFunction func(ctx context.Context) error + startWasCalled bool + builder *scheme.Builder + Fields []interface{} + runnable manager.Runnable +} + +func (m *MockManager) Add(runnable manager.Runnable) error { + m.runnable = runnable + + return nil +} + +func (m *MockManager) Elected() <-chan struct{} { + panic("implement me") +} + +func (m *MockManager) SetFields(i interface{}) error { + if m.Fields == nil { + m.Fields = make([]interface{}, 0) + } + m.Fields = append(m.Fields, i) + + return nil +} + +func (m *MockManager) AddMetricsExtraHandler(path string, handler http.Handler) error { + panic("implement me") +} + +func (m *MockManager) AddHealthzCheck(name string, check healthz.Checker) error { + m.addHealthzCheckWasCalled = true + if m.addHealthzCheckFunction == nil { + return nil + } + return m.addHealthzCheckFunction(name, check) +} + +func (m *MockManager) AddReadyzCheck(name string, check healthz.Checker) error { + m.addReadyzCheckWasCalled = true + if m.addReadyzCheckFunction == nil { + return nil + } + + return m.addReadyzCheckFunction(name, check) +} + +func (m *MockManager) Start(ctx context.Context) error { + m.startWasCalled = true + if m.startFunction == nil { + return nil + } + return m.startFunction(ctx) +} + +func (m *MockManager) GetConfig() *rest.Config { + return &rest.Config{} +} + +func (m *MockManager) GetScheme() *runtime.Scheme { + scheme, _ := m.builder.Build() + return scheme +} + +func (m *MockManager) GetClient() client.Client { + return nil +} + +func (m *MockManager) GetFieldIndexer() client.FieldIndexer { + panic("implement me") +} + +func (m *MockManager) GetCache() cache.Cache { + panic("implement me") +} + +func (m *MockManager) GetEventRecorderFor(name string) record.EventRecorder { + panic("implement me") +} + +func (m *MockManager) GetRESTMapper() meta.RESTMapper { + panic("implement me") +} + +func (m *MockManager) GetAPIReader() client.Reader { + panic("implement me") +} + +func (m *MockManager) GetWebhookServer() *webhook.Server { + panic("implement me") +} + +func (m *MockManager) GetLogger() logr.Logger { + return m.Log +} + +type MockRunFunctionParameters struct { + options zap.Options + metricsAddress string + healthProbeAddress string + enableLeaderElection bool +} + +type MockDriver struct { + parseCommandLineCalled bool + parseCommandLineFunction func() (string, bool, string, zap.Options, error) + runCalled bool + runFunction func(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) + runFunctionParameters MockRunFunctionParameters + newManagerCalled bool + newManagerFunction func(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) + instantiateControllersCalled bool + instantiateControllersFunction func(mgr manager.Manager) []controllers.ManagedController + setupControllerWithManagerCalled bool + setupControllersFunction func(controller controllers.ManagedController, manager manager.Manager) error +} + +func (m *MockDriver) parseCommandLine() (string, bool, string, zap.Options, error) { + m.parseCommandLineCalled = true + if m.parseCommandLineFunction == nil { + return "", true, "", zap.Options{}, nil + } + + return m.parseCommandLineFunction() +} + +func (m *MockDriver) run(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) { + m.runCalled = true + m.runFunctionParameters = MockRunFunctionParameters{ + options: opts, + metricsAddress: metricsAddress, + healthProbeAddress: healthProbeAddress, + enableLeaderElection: enableLeaderElection, + } + if m.runFunction == nil { + return 0, nil + } + + return m.runFunction(opts, metricsAddress, healthProbeAddress, enableLeaderElection) +} + +func (m *MockDriver) newManager(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) { + m.newManagerCalled = true + if m.newManagerFunction == nil { + return &MockManager{}, nil + } + + return m.newManagerFunction(metricsAddress, healthProbeAddress, enableLeaderElection) +} + +func (m *MockDriver) instantiateControllers(mgr manager.Manager) []controllers.ManagedController { + m.instantiateControllersCalled = true + + if m.instantiateControllersFunction == nil { + return []controllers.ManagedController{ + &MockController{}, + } + } + + return m.instantiateControllersFunction(mgr) +} + +func (m *MockDriver) setupControllerWithManager(controller controllers.ManagedController, manager manager.Manager) error { + m.setupControllerWithManagerCalled = true + + if m.setupControllersFunction == nil { + return nil + } + + return m.setupControllersFunction(controller, manager) +} + +type MockError struct { + message string +} + +func (m *MockError) Error() string { + if m.message == "" { + m.message = "mock Error" + } + return m.message +} + +type MockGitlabClient struct { + newClientFunction func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) + listGroupsFunction func(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) + listGroupsByNameFunction func(name string) ([]*gitlab.Group, *gitlab.Response, error) + createGroupFunction func(name string, description string) (*gitlab.Group, *gitlab.Response, error) + updateGroupFunction func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) + +} + +func (m *MockGitlabClient) CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return m.createGroupFunction(name, description) +} + +func (m *MockGitlabClient) UpdateGroup(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return m.updateGroupFunction(id, name, description) +} + +func (m *MockGitlabClient) ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return m.listGroupsByNameFunction(name) +} + +func (m *MockGitlabClient) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return m.newClientFunction(token, options...) +} + +func (m *MockGitlabClient) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { + return m.listGroupsFunction(opt, options...) +} -- GitLab From 4ed43942ea4f7e594c70dfd7c7ef2383f7f8521e Mon Sep 17 00:00:00 2001 From: Jason van Brackel Date: Thu, 17 Jun 2021 15:57:51 -0400 Subject: [PATCH 11/15] feat: working on project controller --- apis/gitlab/v1alpha1/project_types.go | 6 - ...scaffold_test.go => project_types_test.go} | 24 +- controllers/gitlab/mocks_test.go | 860 +++++++++--------- controllers/gitlab/project_controller.go | 26 +- controllers/gitlab/project_controller_test.go | 190 ++++ 5 files changed, 660 insertions(+), 446 deletions(-) rename apis/gitlab/v1alpha1/{project_scaffold_test.go => project_types_test.go} (90%) create mode 100644 controllers/gitlab/project_controller_test.go diff --git a/apis/gitlab/v1alpha1/project_types.go b/apis/gitlab/v1alpha1/project_types.go index b61b3de..e1ea244 100644 --- a/apis/gitlab/v1alpha1/project_types.go +++ b/apis/gitlab/v1alpha1/project_types.go @@ -52,18 +52,12 @@ type ProjectSpec struct { // Language is the programming language of the Project for this application. // +kubebuiler:validation:required Language string `json:"language"` //TODO: Consider a custom type / annotation - - // test Dummy is an example field of Project. Edit project_types.go to remove/update - Dummy string `json:"dummy,omitempty"` } // ProjectStatus defines the observed state of Project type ProjectStatus struct { // URL is the url for the project in GitLab URL string `json:"url"` - - // test Dummy is an example field of Project. Edit project_types.go to remove/update - Dummy string `json:"dummy,omitempty"` } //+kubebuilder:object:root=true diff --git a/apis/gitlab/v1alpha1/project_scaffold_test.go b/apis/gitlab/v1alpha1/project_types_test.go similarity index 90% rename from apis/gitlab/v1alpha1/project_scaffold_test.go rename to apis/gitlab/v1alpha1/project_types_test.go index 14cba53..a44ce93 100644 --- a/apis/gitlab/v1alpha1/project_scaffold_test.go +++ b/apis/gitlab/v1alpha1/project_types_test.go @@ -28,11 +28,9 @@ type testVarsProject = struct { objectItems2 []Project objectList2 ProjectList - // leave scaffold Foo value for testing? testObjectSpec1 ProjectSpec testObjectSpec2 ProjectSpec - // leave scaffold Foo value for testing? testObjectStatus1 ProjectStatus testObjectStatus2 ProjectStatus } @@ -66,13 +64,11 @@ func initVarsProject() testVarsProject { // test_object_list = ProjectList(objectList1MetaType,nil,object_items) testVars.objectList2 = ProjectList{TypeMeta: objectList2MetaType, Items: objectItems2} - // leave scaffold Foo value for testing? - testVars.testObjectSpec1 = ProjectSpec{Dummy: testVars.testSpec, StorageTypes: []string{"type1", "type2"}} - testVars.testObjectSpec2 = ProjectSpec{Dummy: "other value"} + testVars.testObjectSpec1 = ProjectSpec{ StorageTypes: []string{"type1", "type2"}} + testVars.testObjectSpec2 = ProjectSpec{ Name: "a name"} - // leave scaffold Foo value for testing? - testVars.testObjectStatus1 = ProjectStatus{Dummy: testVars.testStatus} - testVars.testObjectStatus2 = ProjectStatus{Dummy: "other value"} + testVars.testObjectStatus1 = ProjectStatus{ URL: testVars.testStatus} + testVars.testObjectStatus2 = ProjectStatus{ URL: "other value"} return testVars } @@ -221,8 +217,8 @@ func TestDeepCopy_DeepCopySpec_Project(t *testing.T) { newObjectList := lTestVars.testObjectSpec1.DeepCopy() - got := newObjectList.Dummy - want := lTestVars.expectedSpec + got := newObjectList.Name + want := lTestVars.testObjectSpec1.Name if got != want { t.Errorf("got %s want %s", got, want) @@ -241,8 +237,8 @@ func TestDeepCopy_DeepCopySpecInto_Project(t *testing.T) { lTestVars.testObjectSpec1.DeepCopyInto(&lTestVars.testObjectSpec2) - got := lTestVars.testObjectSpec2.Dummy - want := lTestVars.expectedSpec + got := lTestVars.testObjectSpec2.Name + want := lTestVars.testObjectSpec1.Name if got != want { t.Errorf("got %s want %s", got, want) @@ -256,7 +252,7 @@ func TestDeepCopy_DeepCopyStatus_Project(t *testing.T) { newObjectStatus := lTestVars.testObjectStatus1.DeepCopy() - got := newObjectStatus.Dummy + got := newObjectStatus.URL want := lTestVars.expectedStatus if got != want { @@ -278,7 +274,7 @@ func TestDeepCopy_DeepCopyStatusInto_Project(t *testing.T) { lTestVars.testObjectStatus1.DeepCopyInto(&lTestVars.testObjectStatus2) - got := lTestVars.testObjectStatus2.Dummy + got := lTestVars.testObjectStatus2.URL want := lTestVars.expectedStatus if got != want { diff --git a/controllers/gitlab/mocks_test.go b/controllers/gitlab/mocks_test.go index ecca40c..c621082 100755 --- a/controllers/gitlab/mocks_test.go +++ b/controllers/gitlab/mocks_test.go @@ -1,424 +1,436 @@ -package gitlab - -import ( - "context" - "github.com/go-logr/logr" - "github.com/jinzhu/copier" - "github.com/xanzy/go-gitlab" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/record" - "net/http" - "sigs.k8s.io/controller-runtime/pkg/cache" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/healthz" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/scheme" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "time" - "valkyrie.dso.mil/valkyrie-api/controllers" -) - -type MockClient struct { - GetFunction func(ctx context.Context, key client.ObjectKey, obj client.Object) error - GetCalled bool - expectedObjects map[client.ObjectKey]client.Object - NotFoundError error - statusWriter client.StatusWriter - createFunction func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error - updateFunction func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error - updateFunctionCalled bool -} - -func (m *MockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { - m.GetCalled = true - if m.GetFunction != nil { - return m.GetFunction(ctx, key, obj) - } - - if m.expectedObjects == nil { - return nil - } - - if m.expectedObjects[key] == nil { - return &errors.StatusError{ - ErrStatus: metav1.Status{ - TypeMeta: metav1.TypeMeta{}, - ListMeta: metav1.ListMeta{}, - Status: string(metav1.StatusReasonNotFound), - Message: "NotFound", - Reason: metav1.StatusReasonNotFound, - Details: nil, - Code: 0, - }, - } - } - - foundObject := m.expectedObjects[key] - - copier.Copy(obj, foundObject) - - return nil -} - -func (m *MockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { - panic("implement me") -} - -func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { - if m.createFunction == nil { - return nil - } - return m.createFunction(ctx, obj, opts...) -} - -func (m *MockClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { - panic("implement me") -} - -func (m *MockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - m.updateFunctionCalled = true - if m.updateFunction == nil { - return nil - } - return m.updateFunction(ctx, obj, opts...) -} - -func (m *MockClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { - panic("implement me") -} - -func (m *MockClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { - panic("implement me") -} - -type MockStatusWriter struct { - updateFunction func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error -} - -func (m MockStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return m.updateFunction(ctx, obj, opts...) -} - -func (m MockStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { - panic("implement me") -} - -func (m *MockClient) Status() client.StatusWriter { - return m.statusWriter -} - -func (m *MockClient) Scheme() *runtime.Scheme { - panic("implement me") -} - -func (m *MockClient) RESTMapper() meta.RESTMapper { - panic("implement me") -} - -type MockContext struct { -} - -func (m *MockContext) Deadline() (deadline time.Time, ok bool) { - panic("implement me") -} - -func (m *MockContext) Done() <-chan struct{} { - panic("implement me") -} - -func (m *MockContext) Err() error { - panic("implement me") -} - -func (m *MockContext) Value(key interface{}) interface{} { - panic("implement me") -} - -type MockLogger struct { - WithValuesKeysAndValues []interface{} - WithValuesCalled bool - WithNameValue string - WithNameCalled bool - loggedMessage string - keysAndValues []interface{} - logFunction func(msg string, keysAndValues ...interface{}) - logLevelCalled string - level int -} - -func (m *MockLogger) Enabled() bool { - return true -} - -func (m *MockLogger) Info(msg string, keysAndValues ...interface{}) { - m.loggedMessage = msg - m.keysAndValues = keysAndValues - m.logLevelCalled = "Info" - if m.logFunction != nil { - m.logFunction(msg, keysAndValues) - return - } -} - -func (m *MockLogger) Error(err error, msg string, keysAndValues ...interface{}) { - m.loggedMessage = msg - m.keysAndValues = keysAndValues - m.logLevelCalled = "Error" - if m.logFunction != nil { - m.logFunction(msg, keysAndValues) - return - } -} - -func (m *MockLogger) V(level int) logr.Logger { - m.level = level - return m -} - -func (m *MockLogger) WithValues(keysAndValues ...interface{}) logr.Logger { - m.WithValuesCalled = true - for i := 0; i < len(keysAndValues); i++ { - m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues[i]) - } - m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues) - - return m -} - -func (m *MockLogger) WithName(name string) logr.Logger { - m.WithNameCalled = true - m.WithNameValue = name - return m -} - -type MockController struct { - setupWithManagerWasCalled bool -} - -func (m *MockController) SetupWithManager(manager manager.Manager) error { - m.setupWithManagerWasCalled = true - return nil -} - -type MockManager struct { - Log logr.Logger - addHealthzCheckFunction func(name string, check healthz.Checker) error - addHealthzCheckWasCalled bool - addReadyzCheckFunction func(name string, check healthz.Checker) error - addReadyzCheckWasCalled bool - startFunction func(ctx context.Context) error - startWasCalled bool - builder *scheme.Builder - Fields []interface{} - runnable manager.Runnable -} - -func (m *MockManager) Add(runnable manager.Runnable) error { - m.runnable = runnable - - return nil -} - -func (m *MockManager) Elected() <-chan struct{} { - panic("implement me") -} - -func (m *MockManager) SetFields(i interface{}) error { - if m.Fields == nil { - m.Fields = make([]interface{}, 0) - } - m.Fields = append(m.Fields, i) - - return nil -} - -func (m *MockManager) AddMetricsExtraHandler(path string, handler http.Handler) error { - panic("implement me") -} - -func (m *MockManager) AddHealthzCheck(name string, check healthz.Checker) error { - m.addHealthzCheckWasCalled = true - if m.addHealthzCheckFunction == nil { - return nil - } - return m.addHealthzCheckFunction(name, check) -} - -func (m *MockManager) AddReadyzCheck(name string, check healthz.Checker) error { - m.addReadyzCheckWasCalled = true - if m.addReadyzCheckFunction == nil { - return nil - } - - return m.addReadyzCheckFunction(name, check) -} - -func (m *MockManager) Start(ctx context.Context) error { - m.startWasCalled = true - if m.startFunction == nil { - return nil - } - return m.startFunction(ctx) -} - -func (m *MockManager) GetConfig() *rest.Config { - return &rest.Config{} -} - -func (m *MockManager) GetScheme() *runtime.Scheme { - scheme, _ := m.builder.Build() - return scheme -} - -func (m *MockManager) GetClient() client.Client { - return nil -} - -func (m *MockManager) GetFieldIndexer() client.FieldIndexer { - panic("implement me") -} - -func (m *MockManager) GetCache() cache.Cache { - panic("implement me") -} - -func (m *MockManager) GetEventRecorderFor(name string) record.EventRecorder { - panic("implement me") -} - -func (m *MockManager) GetRESTMapper() meta.RESTMapper { - panic("implement me") -} - -func (m *MockManager) GetAPIReader() client.Reader { - panic("implement me") -} - -func (m *MockManager) GetWebhookServer() *webhook.Server { - panic("implement me") -} - -func (m *MockManager) GetLogger() logr.Logger { - return m.Log -} - -type MockRunFunctionParameters struct { - options zap.Options - metricsAddress string - healthProbeAddress string - enableLeaderElection bool -} - -type MockDriver struct { - parseCommandLineCalled bool - parseCommandLineFunction func() (string, bool, string, zap.Options, error) - runCalled bool - runFunction func(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) - runFunctionParameters MockRunFunctionParameters - newManagerCalled bool - newManagerFunction func(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) - instantiateControllersCalled bool - instantiateControllersFunction func(mgr manager.Manager) []controllers.ManagedController - setupControllerWithManagerCalled bool - setupControllersFunction func(controller controllers.ManagedController, manager manager.Manager) error -} - -func (m *MockDriver) parseCommandLine() (string, bool, string, zap.Options, error) { - m.parseCommandLineCalled = true - if m.parseCommandLineFunction == nil { - return "", true, "", zap.Options{}, nil - } - - return m.parseCommandLineFunction() -} - -func (m *MockDriver) run(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) { - m.runCalled = true - m.runFunctionParameters = MockRunFunctionParameters{ - options: opts, - metricsAddress: metricsAddress, - healthProbeAddress: healthProbeAddress, - enableLeaderElection: enableLeaderElection, - } - if m.runFunction == nil { - return 0, nil - } - - return m.runFunction(opts, metricsAddress, healthProbeAddress, enableLeaderElection) -} - -func (m *MockDriver) newManager(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) { - m.newManagerCalled = true - if m.newManagerFunction == nil { - return &MockManager{}, nil - } - - return m.newManagerFunction(metricsAddress, healthProbeAddress, enableLeaderElection) -} - -func (m *MockDriver) instantiateControllers(mgr manager.Manager) []controllers.ManagedController { - m.instantiateControllersCalled = true - - if m.instantiateControllersFunction == nil { - return []controllers.ManagedController{ - &MockController{}, - } - } - - return m.instantiateControllersFunction(mgr) -} - -func (m *MockDriver) setupControllerWithManager(controller controllers.ManagedController, manager manager.Manager) error { - m.setupControllerWithManagerCalled = true - - if m.setupControllersFunction == nil { - return nil - } - - return m.setupControllersFunction(controller, manager) -} - -type MockError struct { - message string -} - -func (m *MockError) Error() string { - if m.message == "" { - m.message = "mock Error" - } - return m.message -} - -type MockGitlabClient struct { - newClientFunction func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) - listGroupsFunction func(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) - listGroupsByNameFunction func(name string) ([]*gitlab.Group, *gitlab.Response, error) - createGroupFunction func(name string, description string) (*gitlab.Group, *gitlab.Response, error) - updateGroupFunction func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) - -} - -func (m *MockGitlabClient) CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return m.createGroupFunction(name, description) -} - -func (m *MockGitlabClient) UpdateGroup(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return m.updateGroupFunction(id, name, description) -} - -func (m *MockGitlabClient) ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return m.listGroupsByNameFunction(name) -} - -func (m *MockGitlabClient) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return m.newClientFunction(token, options...) -} - -func (m *MockGitlabClient) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { - return m.listGroupsFunction(opt, options...) -} +package gitlab + +import ( + "context" + "github.com/go-logr/logr" + "github.com/jinzhu/copier" + "github.com/xanzy/go-gitlab" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + "net/http" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/scheme" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "time" + "valkyrie.dso.mil/valkyrie-api/controllers" +) + +type MockClient struct { + GetFunction func(ctx context.Context, key client.ObjectKey, obj client.Object) error + GetCalled bool + expectedObjects map[client.ObjectKey]client.Object + NotFoundError error + statusWriter client.StatusWriter + createFunction func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error + updateFunction func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error + updateFunctionCalled bool + schemeFunction func() *runtime.Scheme +} + +func (m *MockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { + m.GetCalled = true + if m.GetFunction != nil { + return m.GetFunction(ctx, key, obj) + } + + if m.expectedObjects == nil { + return nil + } + + if m.expectedObjects[key] == nil { + return &errors.StatusError{ + ErrStatus: metav1.Status{ + TypeMeta: metav1.TypeMeta{}, + ListMeta: metav1.ListMeta{}, + Status: string(metav1.StatusReasonNotFound), + Message: "NotFound", + Reason: metav1.StatusReasonNotFound, + Details: nil, + Code: 0, + }, + } + } + + foundObject := m.expectedObjects[key] + + copier.Copy(obj, foundObject) + + return nil +} + +func (m *MockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + panic("implement me") +} + +func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + if m.createFunction == nil { + return nil + } + return m.createFunction(ctx, obj, opts...) +} + +func (m *MockClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { + panic("implement me") +} + +func (m *MockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + m.updateFunctionCalled = true + if m.updateFunction == nil { + return nil + } + return m.updateFunction(ctx, obj, opts...) +} + +func (m *MockClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + panic("implement me") +} + +func (m *MockClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { + panic("implement me") +} + +type MockStatusWriter struct { + updateFunction func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error + patchFunction func(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error +} + +func (m MockStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + if m.updateFunction == nil { + return nil + } + return m.updateFunction(ctx, obj, opts...) +} + +func (m MockStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + if m.patchFunction == nil { + return nil + } + + return m.patchFunction(ctx, obj, patch, opts...) +} + +func (m *MockClient) Status() client.StatusWriter { + return m.statusWriter +} + +func (m *MockClient) Scheme() *runtime.Scheme { + if m.schemeFunction == nil { + return &runtime.Scheme{} + } + return m.schemeFunction() +} + +func (m *MockClient) RESTMapper() meta.RESTMapper { + panic("implement me") +} + +type MockContext struct { +} + +func (m *MockContext) Deadline() (deadline time.Time, ok bool) { + panic("implement me") +} + +func (m *MockContext) Done() <-chan struct{} { + panic("implement me") +} + +func (m *MockContext) Err() error { + panic("implement me") +} + +func (m *MockContext) Value(key interface{}) interface{} { + panic("implement me") +} + +type MockLogger struct { + WithValuesKeysAndValues []interface{} + WithValuesCalled bool + WithNameValue string + WithNameCalled bool + loggedMessage string + keysAndValues []interface{} + logFunction func(msg string, keysAndValues ...interface{}) + logLevelCalled string + level int +} + +func (m *MockLogger) Enabled() bool { + return true +} + +func (m *MockLogger) Info(msg string, keysAndValues ...interface{}) { + m.loggedMessage = msg + m.keysAndValues = keysAndValues + m.logLevelCalled = "Info" + if m.logFunction != nil { + m.logFunction(msg, keysAndValues) + return + } +} + +func (m *MockLogger) Error(err error, msg string, keysAndValues ...interface{}) { + m.loggedMessage = msg + m.keysAndValues = keysAndValues + m.logLevelCalled = "Error" + if m.logFunction != nil { + m.logFunction(msg, keysAndValues) + return + } +} + +func (m *MockLogger) V(level int) logr.Logger { + m.level = level + return m +} + +func (m *MockLogger) WithValues(keysAndValues ...interface{}) logr.Logger { + m.WithValuesCalled = true + for i := 0; i < len(keysAndValues); i++ { + m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues[i]) + } + m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues) + + return m +} + +func (m *MockLogger) WithName(name string) logr.Logger { + m.WithNameCalled = true + m.WithNameValue = name + return m +} + +type MockController struct { + setupWithManagerWasCalled bool +} + +func (m *MockController) SetupWithManager(manager manager.Manager) error { + m.setupWithManagerWasCalled = true + return nil +} + +type MockManager struct { + Log logr.Logger + addHealthzCheckFunction func(name string, check healthz.Checker) error + addHealthzCheckWasCalled bool + addReadyzCheckFunction func(name string, check healthz.Checker) error + addReadyzCheckWasCalled bool + startFunction func(ctx context.Context) error + startWasCalled bool + builder *scheme.Builder + Fields []interface{} + runnable manager.Runnable +} + +func (m *MockManager) Add(runnable manager.Runnable) error { + m.runnable = runnable + + return nil +} + +func (m *MockManager) Elected() <-chan struct{} { + panic("implement me") +} + +func (m *MockManager) SetFields(i interface{}) error { + if m.Fields == nil { + m.Fields = make([]interface{}, 0) + } + m.Fields = append(m.Fields, i) + + return nil +} + +func (m *MockManager) AddMetricsExtraHandler(path string, handler http.Handler) error { + panic("implement me") +} + +func (m *MockManager) AddHealthzCheck(name string, check healthz.Checker) error { + m.addHealthzCheckWasCalled = true + if m.addHealthzCheckFunction == nil { + return nil + } + return m.addHealthzCheckFunction(name, check) +} + +func (m *MockManager) AddReadyzCheck(name string, check healthz.Checker) error { + m.addReadyzCheckWasCalled = true + if m.addReadyzCheckFunction == nil { + return nil + } + + return m.addReadyzCheckFunction(name, check) +} + +func (m *MockManager) Start(ctx context.Context) error { + m.startWasCalled = true + if m.startFunction == nil { + return nil + } + return m.startFunction(ctx) +} + +func (m *MockManager) GetConfig() *rest.Config { + return &rest.Config{} +} + +func (m *MockManager) GetScheme() *runtime.Scheme { + scheme, _ := m.builder.Build() + return scheme +} + +func (m *MockManager) GetClient() client.Client { + return nil +} + +func (m *MockManager) GetFieldIndexer() client.FieldIndexer { + panic("implement me") +} + +func (m *MockManager) GetCache() cache.Cache { + panic("implement me") +} + +func (m *MockManager) GetEventRecorderFor(name string) record.EventRecorder { + panic("implement me") +} + +func (m *MockManager) GetRESTMapper() meta.RESTMapper { + panic("implement me") +} + +func (m *MockManager) GetAPIReader() client.Reader { + panic("implement me") +} + +func (m *MockManager) GetWebhookServer() *webhook.Server { + panic("implement me") +} + +func (m *MockManager) GetLogger() logr.Logger { + return m.Log +} + +type MockRunFunctionParameters struct { + options zap.Options + metricsAddress string + healthProbeAddress string + enableLeaderElection bool +} + +type MockDriver struct { + parseCommandLineCalled bool + parseCommandLineFunction func() (string, bool, string, zap.Options, error) + runCalled bool + runFunction func(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) + runFunctionParameters MockRunFunctionParameters + newManagerCalled bool + newManagerFunction func(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) + instantiateControllersCalled bool + instantiateControllersFunction func(mgr manager.Manager) []controllers.ManagedController + setupControllerWithManagerCalled bool + setupControllersFunction func(controller controllers.ManagedController, manager manager.Manager) error +} + +func (m *MockDriver) parseCommandLine() (string, bool, string, zap.Options, error) { + m.parseCommandLineCalled = true + if m.parseCommandLineFunction == nil { + return "", true, "", zap.Options{}, nil + } + + return m.parseCommandLineFunction() +} + +func (m *MockDriver) run(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) { + m.runCalled = true + m.runFunctionParameters = MockRunFunctionParameters{ + options: opts, + metricsAddress: metricsAddress, + healthProbeAddress: healthProbeAddress, + enableLeaderElection: enableLeaderElection, + } + if m.runFunction == nil { + return 0, nil + } + + return m.runFunction(opts, metricsAddress, healthProbeAddress, enableLeaderElection) +} + +func (m *MockDriver) newManager(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) { + m.newManagerCalled = true + if m.newManagerFunction == nil { + return &MockManager{}, nil + } + + return m.newManagerFunction(metricsAddress, healthProbeAddress, enableLeaderElection) +} + +func (m *MockDriver) instantiateControllers(mgr manager.Manager) []controllers.ManagedController { + m.instantiateControllersCalled = true + + if m.instantiateControllersFunction == nil { + return []controllers.ManagedController{ + &MockController{}, + } + } + + return m.instantiateControllersFunction(mgr) +} + +func (m *MockDriver) setupControllerWithManager(controller controllers.ManagedController, manager manager.Manager) error { + m.setupControllerWithManagerCalled = true + + if m.setupControllersFunction == nil { + return nil + } + + return m.setupControllersFunction(controller, manager) +} + +type MockError struct { + message string +} + +func (m *MockError) Error() string { + if m.message == "" { + m.message = "mock Error" + } + return m.message +} + +type MockGitlabClient struct { + newClientFunction func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) + listGroupsFunction func(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) + listGroupsByNameFunction func(name string) ([]*gitlab.Group, *gitlab.Response, error) + createGroupFunction func(name string, description string) (*gitlab.Group, *gitlab.Response, error) + updateGroupFunction func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) + +} + +func (m *MockGitlabClient) CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return m.createGroupFunction(name, description) +} + +func (m *MockGitlabClient) UpdateGroup(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return m.updateGroupFunction(id, name, description) +} + +func (m *MockGitlabClient) ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return m.listGroupsByNameFunction(name) +} + +func (m *MockGitlabClient) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return m.newClientFunction(token, options...) +} + +func (m *MockGitlabClient) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { + return m.listGroupsFunction(opt, options...) +} diff --git a/controllers/gitlab/project_controller.go b/controllers/gitlab/project_controller.go index ac64eb1..b08e564 100644 --- a/controllers/gitlab/project_controller.go +++ b/controllers/gitlab/project_controller.go @@ -18,7 +18,6 @@ package gitlab import ( "context" - "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" @@ -32,6 +31,7 @@ type ProjectReconciler struct { client.Client Log logr.Logger Scheme *runtime.Scheme + gitlabClient Client } //+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=projects,verbs=get;list;watch;create;update;patch;delete @@ -47,17 +47,39 @@ type ProjectReconciler struct { // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.2/pkg/reconcile + +// errors +const ( + errorWhileLookingUpProject string = "error while looking up project" +) func (r *ProjectReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = r.Log.WithValues("project", req.NamespacedName) - // your logic here + if _, err := r.getProject(ctx, req); err != nil { + r.Log.Error(err, errorWhileLookingUpProject, "request", req) + return ctrl.Result{Requeue: client.IgnoreNotFound(err) != nil}, client.IgnoreNotFound(err) + } return ctrl.Result{}, nil } // SetupWithManager sets up the controller with the Manager. func (r *ProjectReconciler) SetupWithManager(mgr ctrl.Manager) error { + r.gitlabClient = ClientImpl{} + return ctrl.NewControllerManagedBy(mgr). For(&gitlabv1alpha1.Project{}). + Owns(&gitlabv1alpha1.Pipeline{}). Complete(r) } + +func (r *ProjectReconciler) getProject(ctx context.Context, request ctrl.Request) (*gitlabv1alpha1.Project, error) { + var project gitlabv1alpha1.Project + if err := r.Client.Get(ctx, request.NamespacedName, &project); err != nil { + if client.IgnoreNotFound(err) == nil { + return nil, err + } + return &project, err + } + return &project, nil +} diff --git a/controllers/gitlab/project_controller_test.go b/controllers/gitlab/project_controller_test.go new file mode 100644 index 0000000..11dafb6 --- /dev/null +++ b/controllers/gitlab/project_controller_test.go @@ -0,0 +1,190 @@ +package gitlab + +import ( + "context" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" +) + +func getControllerWithMocksInGreenTestState() (ProjectReconciler, *MockManager, *MockLogger) { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Project{}, &gitlabv1alpha1.ProjectList{}) + scheme, _ := builder.Build() + + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + clientMock := MockClient{ + statusWriter: MockStatusWriter{}, + expectedObjects: make(map[client.ObjectKey]client.Object), + } + clientMock.expectedObjects[getRequestWithDefaultNamespacedTestProject().NamespacedName] = &gitlabv1alpha1.Project{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "TestProject", + Namespace: "default", + }, + Spec: gitlabv1alpha1.ProjectSpec{ + Name: "TestProject", + Path: "https://example.com/TestProject", + ImpactLevel: "2", + StorageTypes: nil, + VirtualService: "default", + ServicePort: 8081, + Language: "golang", + }, + Status: gitlabv1alpha1.ProjectStatus{}, + } + mockManager := &MockManager{ + Log: &loggerMock, + builder: builder, + } + + sut := ProjectReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + return sut, mockManager, &loggerMock +} + +func getRequestWithDefaultNamespacedTestProject() ctrl.Request { + return ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "TestProject", + }, + } +} + +var _ = Describe("SetupWithManager", func() { + sut, mgr, _ := getControllerWithMocksInGreenTestState() + + err := sut.SetupWithManager(mgr) + + It("should setup the default gitlab client", func() { + Expect(sut.gitlabClient).To(BeAssignableToTypeOf(ClientImpl{})) + }) + It("should return no error", func() { + Expect(err).To(BeNil()) + }) +}) + +var _ = Describe("reconcile", func() { + Context("green state", func() { + sut, _, _ := getControllerWithMocksInGreenTestState() + request := getRequestWithDefaultNamespacedTestProject() + result, err := sut.Reconcile(context.TODO(), request) + It("should return an empty result", func() { + Expect(result).To(Equal(ctrl.Result{})) + }) + It("should return no error", func() { + Expect(err).To(BeNil()) + }) + }) + Context("project isn't found", func() { + sut, _, log := getControllerWithMocksInGreenTestState() + request := getRequestWithDefaultNamespacedTestProject() + request.NamespacedName.Name = "not going to find it" + result, err := sut.Reconcile(context.TODO(), request) + It("should return no error", func() { + Expect(err).To(BeNil()) + }) + It("should not requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: false})) + }) + It("should log that there was an error looking for the project", func() { + Expect(log.loggedMessage).To(Equal(errorWhileLookingUpProject)) + }) + }) + Context("getProject returns an error", func() { + sut, _, log := getControllerWithMocksInGreenTestState() + failingClient := MockClient{ + GetFunction: func(ctx context.Context, key client.ObjectKey, obj client.Object) error { + return &MockError{ + message: "Error while doing a get.", + } + }, + } + sut.Client = &failingClient + request := ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "notGonnaFindIt", + }, + } + result, err := sut.Reconcile(context.TODO(), request) + It("should return an error", func() { + Expect(err).ToNot(BeNil()) + }) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log that there was an error looking for the project", func() { + Expect(log.loggedMessage).To(Equal(errorWhileLookingUpProject)) + }) + }) +}) + +var _ = Describe("getProject", func() { + Context("green state", func() { + sut, _, _ := getControllerWithMocksInGreenTestState() + request := getRequestWithDefaultNamespacedTestProject() + project, err := sut.getProject(context.TODO(), request) + It("should return a project from the client", func() { + Expect(project.Name).To(Equal(request.Name)) + Expect(project.Namespace).To(Equal(request.Namespace)) + }) + It("should return no error", func() { + Expect(err).To(BeNil()) + }) + }) + Context("object is not found.", func() { + sut, _, _ := getControllerWithMocksInGreenTestState() + request := ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "notGonnaFindIt", + }, + } + project, err := sut.getProject(context.TODO(), request) + It("should return no project", func() { + Expect(project).To(BeNil()) + }) + It("should return a not found error.", func() { + Expect(err).ToNot(BeNil()) + Expect(client.IgnoreNotFound(err)).To(BeNil()) + }) + }) + Context("an error occurs", func() { + sut, _, _ := getControllerWithMocksInGreenTestState() + failingClient := MockClient{ + GetFunction: func(ctx context.Context, key client.ObjectKey, obj client.Object) error { + return &MockError{ + message: "Error while doing a get.", + } + }, + } + sut.Client = &failingClient + request := ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "notGonnaFindIt", + }, + } + project, err := sut.getProject(context.TODO(), request) + It("should return an empty project", func() { + Expect(project).ToNot(BeNil()) + }) + It("should return an error.", func() { + Expect(err).ToNot(BeNil()) + }) + }) +}) \ No newline at end of file -- GitLab From d4d403e82181efd0cfcead0925d5ab75aed8b27b Mon Sep 17 00:00:00 2001 From: Jason van Brackel Date: Thu, 17 Jun 2021 16:02:11 -0400 Subject: [PATCH 12/15] fix: force unix lf --- controllers/gitlab/gitlab_client.go | 111 +- controllers/gitlab/group_controller.go | 630 ++--- controllers/gitlab/group_controller_test.go | 2310 +++++++++---------- 3 files changed, 1525 insertions(+), 1526 deletions(-) diff --git a/controllers/gitlab/gitlab_client.go b/controllers/gitlab/gitlab_client.go index abc9052..d9d574b 100644 --- a/controllers/gitlab/gitlab_client.go +++ b/controllers/gitlab/gitlab_client.go @@ -1,56 +1,55 @@ -package gitlab - -import gitlab "github.com/xanzy/go-gitlab" - -// Client is a facade for go-gitlab -type Client interface { - NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) - ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) - ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) - CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) - UpdateGroup(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) - -} - -// ClientImpl is the Default implementation for the facade. -type ClientImpl struct { - client *gitlab.Client -} - -// NewClient is a facade for the go-gitlab client NewClient call -func (c ClientImpl) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - client, err := gitlab.NewClient(token, options...) - c.client = client - return client, err -} - -// ListGroups is a facade for the go-gitlab client.Groups.ListGroups call. -func (c ClientImpl) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { - return c.client.Groups.ListGroups(opt, options...) -} - -// ListGroupsByName is a facade for the go-gitlab client.Groups.ListGroups call, with the group name as a search option. -func (c ClientImpl) ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return c.client.Groups.ListGroups( - &gitlab.ListGroupsOptions{ - Search: &name, - }) -} - -// CreateGroup is a facade for the go-gitlab client.Groups.CreateGroup -func (c ClientImpl) CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) { - opt := gitlab.CreateGroupOptions{ - Name: &name, - Description: &description, - } - return c.client.Groups.CreateGroup(&opt) -} - -// UpdateGroup is a facade for the go-gitlab client.Group.UpdateGroup -func (c ClientImpl) UpdateGroup(id string, name string, descrption string) (*gitlab.Group, *gitlab.Response, error) { - opt := gitlab.UpdateGroupOptions{ - Name: &name, - Description: &descrption, - } - return c.client.Groups.UpdateGroup(id, &opt) -} +package gitlab + +import gitlab "github.com/xanzy/go-gitlab" + +// Client is a facade for go-gitlab +type Client interface { + NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) + ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) + ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) + CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) + UpdateGroup(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) +} + +// ClientImpl is the Default implementation for the facade. +type ClientImpl struct { + client *gitlab.Client +} + +// NewClient is a facade for the go-gitlab client NewClient call +func (c ClientImpl) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + client, err := gitlab.NewClient(token, options...) + c.client = client + return client, err +} + +// ListGroups is a facade for the go-gitlab client.Groups.ListGroups call. +func (c ClientImpl) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { + return c.client.Groups.ListGroups(opt, options...) +} + +// ListGroupsByName is a facade for the go-gitlab client.Groups.ListGroups call, with the group name as a search option. +func (c ClientImpl) ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return c.client.Groups.ListGroups( + &gitlab.ListGroupsOptions{ + Search: &name, + }) +} + +// CreateGroup is a facade for the go-gitlab client.Groups.CreateGroup +func (c ClientImpl) CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) { + opt := gitlab.CreateGroupOptions{ + Name: &name, + Description: &description, + } + return c.client.Groups.CreateGroup(&opt) +} + +// UpdateGroup is a facade for the go-gitlab client.Group.UpdateGroup +func (c ClientImpl) UpdateGroup(id string, name string, descrption string) (*gitlab.Group, *gitlab.Response, error) { + opt := gitlab.UpdateGroupOptions{ + Name: &name, + Description: &descrption, + } + return c.client.Groups.UpdateGroup(id, &opt) +} diff --git a/controllers/gitlab/group_controller.go b/controllers/gitlab/group_controller.go index db8c208..1917714 100644 --- a/controllers/gitlab/group_controller.go +++ b/controllers/gitlab/group_controller.go @@ -1,316 +1,316 @@ -/* -Copyright 2021. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package gitlab - -import ( - "context" - "fmt" - "github.com/go-logr/logr" - "github.com/xanzy/go-gitlab" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "time" - gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" -) - -// Errors -const ( - errorUnableToFetchGroup = "unable to fetch group" - errorUnableToFetchGitlabCredentials = "unable to fetch gitlab credentials" - errorUnableToFetchSecret = "unable to fetch secret from gitlab credentials" - errorUnableToCreateGitlabClient = "unable to create gitlab client" - errorWhileSearchingGroups = "Error while searching groups." - errorWhileCreatingGitlabGroup = "Error while creating GitLab group." - errorWhileUpdatingGitlabGroup = "Error while updating GitLab group." - errorUnableToUpdateStatus = "Unable to update status." - errorUnableToProcessProjects = "Unable to process projects" - errorTryingToCreateProject = "Error trying to create project" - errorLookingUpProject = "Error looking up project" - errorUpdatingProject = "Error updating project" -) - -// Group Status -const ( - CredentialNotFound = "CredentialNotFound" - CredentialSecretNotFound = "CredentialSecretNotFound" - GroupCreated = "Created" - GroupOK = "OK" -) - -const valkyrie = "valkyrie" - - -// GroupReconciler reconciles the state of a Gitlab Group, for each reconciliation loop it will log in to Gitlab -// with credentials in the secret provided. It will then check the state of the Group, and create it if necessary. -type GroupReconciler struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - gitlabClient Client -} - -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups/finalizers,verbs=update -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=gitlabcredentials,verbs=get;list;watch -//+kubebuilder:rbac:groups=core,resources=secretreference,verbs=get;list;watch -//+kubebuilder:rbac:groups=core,resources=secret,verbs=get;list;watch -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=projects,verbs=get;list;watch;create;update;patch;delete - -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// The reconcile loop for the Gitlab Group will update the Group and any of the child Project API Objects. -func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := r.Log.WithValues("group", req.NamespacedName) - - group := &gitlabv1alpha1.Group{} - - // Get the Group Object - if err := r.Get(ctx, req.NamespacedName, group); err != nil { - log.Error(err, errorUnableToFetchGroup, "group", req.NamespacedName.Name) - // we'll ignore not-found errors, since they can't be fixed by an immediate - // requeue (we'll need to wait for a new notification), and we can get them - // on deleted requests. - return ctrl.Result{}, client.IgnoreNotFound(err) - } - - // Get the Gitlab Credentials - var gitLabCredentialsName = types.NamespacedName{ - Namespace: req.Namespace, - Name: group.Spec.GitlabCredentialsName, - } - - var gitlabCredentials gitlabv1alpha1.GitlabCredentials - if err := r.Get(ctx, gitLabCredentialsName, &gitlabCredentials); err != nil { - - log.Error(err, errorUnableToFetchGitlabCredentials) - group.Status.State = CredentialNotFound - _ = r.updateStatus(ctx, 0, group) - return ctrl.Result{Requeue: true}, err - } - - var secretName = types.NamespacedName{ - Namespace: gitlabCredentials.Spec.AccessToken.Namespace, - Name: gitlabCredentials.Spec.AccessToken.Name, - } - - var secret v1.Secret - // Get the Secret - if err := r.Get(ctx, secretName, &secret); err != nil { - - log.Error(err, errorUnableToFetchSecret) - group.Status.State = CredentialSecretNotFound - _ = r.updateStatus(ctx, 0, group) - - return ctrl.Result{Requeue: true}, err - } - - // Login to Gitlab - var accessToken = string(secret.Data[gitlabCredentials.Spec.AccessTokenKey]) - - var err error - if _, err = r.gitlabClient.NewClient(accessToken, gitlab.WithBaseURL(gitlabCredentials.Spec.URL)); err != nil { - log.Error(err, errorUnableToCreateGitlabClient, "username", gitlabCredentials.Spec.Username, "url", gitlabCredentials.Spec.URL) - return ctrl.Result{Requeue: true}, err - } - - // See if the Group Exists - var gitlabGroup *gitlab.Group - found, _, gitlabGroup, err := r.groupExists(group) - if err != nil { - log.Error(err, errorWhileSearchingGroups, "status") - return ctrl.Result{Requeue: true}, err - } - - if !found { - if gitlabGroup, _, err = r.createGroup(group); err != nil { - - log.Error(err, errorWhileCreatingGitlabGroup, "group", group) - return ctrl.Result{Requeue: true}, err - } - } else { - if gitlabGroup, _, err = r.updateGroup(group); err != nil { - log.Error(err, errorWhileUpdatingGitlabGroup, "group", group) - return ctrl.Result{Requeue: true}, err - } - } - - if err := r.updateStatus(ctx, gitlabGroup.ID, group); err != nil { - log.Error(err, errorUnableToUpdateStatus, "group", group) - return ctrl.Result{ Requeue: true }, err - } - - if err := r.processProjects(ctx, group, group.Spec.ProjectSpecs); err != nil { - log.Error(err, errorUnableToProcessProjects, "group", group) - return ctrl.Result{Requeue: true}, err - } - - return ctrl.Result{}, nil -} - -func (r *GroupReconciler) groupExists(group *gitlabv1alpha1.Group) (bool, *gitlab.Response, *gitlab.Group, error) { - - var grouplist []*gitlab.Group - var response *gitlab.Response - var err error - if grouplist, response, err = r.gitlabClient.ListGroupsByName(group.Spec.Name); err != nil { - return false, response, nil, err - } - - // TODO: (jvb) For work beyond the MVP we'll need to handle pagination. May be superceeded by adam's client work. - found := false - for _, groupInList := range grouplist { - found = group.Spec.Name == groupInList.Name - if found { - return found, response, groupInList, nil - } - } - return found, response, nil, nil -} - -func (r *GroupReconciler) createGroup(group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { - gitlabGroup, response, err := r.gitlabClient.CreateGroup(group.Spec.Name, group.Spec.Description) - - if err == nil { - group.Status.CreatedTime = metav1.Time{ - Time: time.Now(), - } - group.ObjectMeta.Annotations["ID"] = fmt.Sprint(gitlabGroup.ID) - group.ObjectMeta.Annotations["Creator"] = valkyrie - group.Status.State = GroupCreated - } - - return gitlabGroup, response, err -} - -func (r *GroupReconciler) updateGroup(group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { - gitlabGroup, response, err := r.gitlabClient.UpdateGroup(group.ObjectMeta.Annotations["ID"], group.Spec.Name, group.Spec.Description) - - if err == nil { - group.Status.State = GroupOK - group.Status.LastUpdatedTime = metav1.Time{ - Time: time.Now(), - } - } - - return gitlabGroup, response, err -} - - -func (r *GroupReconciler) processProjects(ctx context.Context, group *gitlabv1alpha1.Group, specs []gitlabv1alpha1.ProjectSpec) error { - for _, projectSpec := range specs { - if project, foundProject, err := r.getProject(ctx, group, projectSpec); err != nil { - r.Log.Error(err, errorLookingUpProject, "name", projectSpec.Name) - return err - } else if foundProject { - if _, err := r.updateProject(ctx, projectSpec, project); err != nil { - - r.Log.Error(err, errorUpdatingProject, "project", project) - } - } else { - if err := r.createProject(ctx, group, projectSpec); err != nil { - - r.Log.Error(err, errorTryingToCreateProject, "project", projectSpec) - return err - } - } - } - return nil -} - -func (r *GroupReconciler) getProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec) (*gitlabv1alpha1.Project, bool, error) { - var project gitlabv1alpha1.Project - projectName := types.NamespacedName{ - Namespace: group.Namespace, - Name: fmt.Sprintf("%s-%s", group.Name, spec.Name), - } - err := r.Get(ctx, projectName, &project) - - projectNotFound := client.IgnoreNotFound(err) - - if projectNotFound == nil { - return nil, false, nil - } - - return &project, true, err -} - -func (r *GroupReconciler) createProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec) error { - blockOwnerDeletion := true - managingController := true - return r.Create(ctx, &gitlabv1alpha1.Project{ - ObjectMeta: ctrl.ObjectMeta{ - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: group.APIVersion, - Kind: group.Kind, - Name: group.Name, - UID: group.UID, - Controller: &managingController, - BlockOwnerDeletion: &blockOwnerDeletion, - }, - }, - }, - Spec: gitlabv1alpha1.ProjectSpec{ - Name: spec.Name, - Path: spec.Path, - ImpactLevel: spec.ImpactLevel, - StorageTypes: spec.StorageTypes, - VirtualService: spec.VirtualService, - ServicePort: spec.ServicePort, - Language: spec.Language, - }, - }) -} - -func (r *GroupReconciler) updateProject(ctx context.Context, spec gitlabv1alpha1.ProjectSpec, project *gitlabv1alpha1.Project) (*gitlabv1alpha1.Project, error) { - project.Spec.Name = spec.Name - project.Spec.Path = spec.Path - project.Spec.ImpactLevel = spec.ImpactLevel - project.Spec.StorageTypes = spec.StorageTypes - project.Spec.VirtualService = spec.VirtualService - project.Spec.ServicePort = spec.ServicePort - project.Spec.Language = spec.Language - return project, r.Update(ctx, project) -} - -func (r *GroupReconciler) updateStatus(ctx context.Context, id int, group *gitlabv1alpha1.Group) error { - if id != 0 { - id64 := int64(id) - group.Status.GitlabID = &id64 - group.Status.LastUpdatedTime = metav1.Time{ - Time: time.Now(), - } - } - - return r.Status().Update(ctx, group) -} - -// SetupWithManager sets up the controller with the Manager. -func (r *GroupReconciler) SetupWithManager(mgr ctrl.Manager) error { - // Setup a default GitlabClient implementation - r.gitlabClient = &ClientImpl{} - - return ctrl.NewControllerManagedBy(mgr). - For(&gitlabv1alpha1.Group{}). - Owns(&gitlabv1alpha1.Project{}). - Complete(r) +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gitlab + +import ( + "context" + "fmt" + "github.com/go-logr/logr" + "github.com/xanzy/go-gitlab" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "time" + gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" +) + +// Errors +const ( + errorUnableToFetchGroup = "unable to fetch group" + errorUnableToFetchGitlabCredentials = "unable to fetch gitlab credentials" + errorUnableToFetchSecret = "unable to fetch secret from gitlab credentials" + errorUnableToCreateGitlabClient = "unable to create gitlab client" + errorWhileSearchingGroups = "Error while searching groups." + errorWhileCreatingGitlabGroup = "Error while creating GitLab group." + errorWhileUpdatingGitlabGroup = "Error while updating GitLab group." + errorUnableToUpdateStatus = "Unable to update status." + errorUnableToProcessProjects = "Unable to process projects" + errorTryingToCreateProject = "Error trying to create project" + errorLookingUpProject = "Error looking up project" + errorUpdatingProject = "Error updating project" +) + +// Group Status +const ( + CredentialNotFound = "CredentialNotFound" + CredentialSecretNotFound = "CredentialSecretNotFound" + GroupCreated = "Created" + GroupOK = "OK" +) + +const valkyrie = "valkyrie" + + +// GroupReconciler reconciles the state of a Gitlab Group, for each reconciliation loop it will log in to Gitlab +// with credentials in the secret provided. It will then check the state of the Group, and create it if necessary. +type GroupReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + gitlabClient Client +} + +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups/finalizers,verbs=update +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=gitlabcredentials,verbs=get;list;watch +//+kubebuilder:rbac:groups=core,resources=secretreference,verbs=get;list;watch +//+kubebuilder:rbac:groups=core,resources=secret,verbs=get;list;watch +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=projects,verbs=get;list;watch;create;update;patch;delete + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// The reconcile loop for the Gitlab Group will update the Group and any of the child Project API Objects. +func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := r.Log.WithValues("group", req.NamespacedName) + + group := &gitlabv1alpha1.Group{} + + // Get the Group Object + if err := r.Get(ctx, req.NamespacedName, group); err != nil { + log.Error(err, errorUnableToFetchGroup, "group", req.NamespacedName.Name) + // we'll ignore not-found errors, since they can't be fixed by an immediate + // requeue (we'll need to wait for a new notification), and we can get them + // on deleted requests. + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + // Get the Gitlab Credentials + var gitLabCredentialsName = types.NamespacedName{ + Namespace: req.Namespace, + Name: group.Spec.GitlabCredentialsName, + } + + var gitlabCredentials gitlabv1alpha1.GitlabCredentials + if err := r.Get(ctx, gitLabCredentialsName, &gitlabCredentials); err != nil { + + log.Error(err, errorUnableToFetchGitlabCredentials) + group.Status.State = CredentialNotFound + _ = r.updateStatus(ctx, 0, group) + return ctrl.Result{Requeue: true}, err + } + + var secretName = types.NamespacedName{ + Namespace: gitlabCredentials.Spec.AccessToken.Namespace, + Name: gitlabCredentials.Spec.AccessToken.Name, + } + + var secret v1.Secret + // Get the Secret + if err := r.Get(ctx, secretName, &secret); err != nil { + + log.Error(err, errorUnableToFetchSecret) + group.Status.State = CredentialSecretNotFound + _ = r.updateStatus(ctx, 0, group) + + return ctrl.Result{Requeue: true}, err + } + + // Login to Gitlab + var accessToken = string(secret.Data[gitlabCredentials.Spec.AccessTokenKey]) + + var err error + if _, err = r.gitlabClient.NewClient(accessToken, gitlab.WithBaseURL(gitlabCredentials.Spec.URL)); err != nil { + log.Error(err, errorUnableToCreateGitlabClient, "username", gitlabCredentials.Spec.Username, "url", gitlabCredentials.Spec.URL) + return ctrl.Result{Requeue: true}, err + } + + // See if the Group Exists + var gitlabGroup *gitlab.Group + found, _, gitlabGroup, err := r.groupExists(group) + if err != nil { + log.Error(err, errorWhileSearchingGroups, "status") + return ctrl.Result{Requeue: true}, err + } + + if !found { + if gitlabGroup, _, err = r.createGroup(group); err != nil { + + log.Error(err, errorWhileCreatingGitlabGroup, "group", group) + return ctrl.Result{Requeue: true}, err + } + } else { + if gitlabGroup, _, err = r.updateGroup(group); err != nil { + log.Error(err, errorWhileUpdatingGitlabGroup, "group", group) + return ctrl.Result{Requeue: true}, err + } + } + + if err := r.updateStatus(ctx, gitlabGroup.ID, group); err != nil { + log.Error(err, errorUnableToUpdateStatus, "group", group) + return ctrl.Result{ Requeue: true }, err + } + + if err := r.processProjects(ctx, group, group.Spec.ProjectSpecs); err != nil { + log.Error(err, errorUnableToProcessProjects, "group", group) + return ctrl.Result{Requeue: true}, err + } + + return ctrl.Result{}, nil +} + +func (r *GroupReconciler) groupExists(group *gitlabv1alpha1.Group) (bool, *gitlab.Response, *gitlab.Group, error) { + + var grouplist []*gitlab.Group + var response *gitlab.Response + var err error + if grouplist, response, err = r.gitlabClient.ListGroupsByName(group.Spec.Name); err != nil { + return false, response, nil, err + } + + // TODO: (jvb) For work beyond the MVP we'll need to handle pagination. May be superceeded by adam's client work. + found := false + for _, groupInList := range grouplist { + found = group.Spec.Name == groupInList.Name + if found { + return found, response, groupInList, nil + } + } + return found, response, nil, nil +} + +func (r *GroupReconciler) createGroup(group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { + gitlabGroup, response, err := r.gitlabClient.CreateGroup(group.Spec.Name, group.Spec.Description) + + if err == nil { + group.Status.CreatedTime = metav1.Time{ + Time: time.Now(), + } + group.ObjectMeta.Annotations["ID"] = fmt.Sprint(gitlabGroup.ID) + group.ObjectMeta.Annotations["Creator"] = valkyrie + group.Status.State = GroupCreated + } + + return gitlabGroup, response, err +} + +func (r *GroupReconciler) updateGroup(group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { + gitlabGroup, response, err := r.gitlabClient.UpdateGroup(group.ObjectMeta.Annotations["ID"], group.Spec.Name, group.Spec.Description) + + if err == nil { + group.Status.State = GroupOK + group.Status.LastUpdatedTime = metav1.Time{ + Time: time.Now(), + } + } + + return gitlabGroup, response, err +} + + +func (r *GroupReconciler) processProjects(ctx context.Context, group *gitlabv1alpha1.Group, specs []gitlabv1alpha1.ProjectSpec) error { + for _, projectSpec := range specs { + if project, foundProject, err := r.getProject(ctx, group, projectSpec); err != nil { + r.Log.Error(err, errorLookingUpProject, "name", projectSpec.Name) + return err + } else if foundProject { + if _, err := r.updateProject(ctx, projectSpec, project); err != nil { + + r.Log.Error(err, errorUpdatingProject, "project", project) + } + } else { + if err := r.createProject(ctx, group, projectSpec); err != nil { + + r.Log.Error(err, errorTryingToCreateProject, "project", projectSpec) + return err + } + } + } + return nil +} + +func (r *GroupReconciler) getProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec) (*gitlabv1alpha1.Project, bool, error) { + var project gitlabv1alpha1.Project + projectName := types.NamespacedName{ + Namespace: group.Namespace, + Name: fmt.Sprintf("%s-%s", group.Name, spec.Name), + } + err := r.Get(ctx, projectName, &project) + + projectNotFound := client.IgnoreNotFound(err) + + if projectNotFound == nil { + return nil, false, nil + } + + return &project, true, err +} + +func (r *GroupReconciler) createProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec) error { + blockOwnerDeletion := true + managingController := true + return r.Create(ctx, &gitlabv1alpha1.Project{ + ObjectMeta: ctrl.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: group.APIVersion, + Kind: group.Kind, + Name: group.Name, + UID: group.UID, + Controller: &managingController, + BlockOwnerDeletion: &blockOwnerDeletion, + }, + }, + }, + Spec: gitlabv1alpha1.ProjectSpec{ + Name: spec.Name, + Path: spec.Path, + ImpactLevel: spec.ImpactLevel, + StorageTypes: spec.StorageTypes, + VirtualService: spec.VirtualService, + ServicePort: spec.ServicePort, + Language: spec.Language, + }, + }) +} + +func (r *GroupReconciler) updateProject(ctx context.Context, spec gitlabv1alpha1.ProjectSpec, project *gitlabv1alpha1.Project) (*gitlabv1alpha1.Project, error) { + project.Spec.Name = spec.Name + project.Spec.Path = spec.Path + project.Spec.ImpactLevel = spec.ImpactLevel + project.Spec.StorageTypes = spec.StorageTypes + project.Spec.VirtualService = spec.VirtualService + project.Spec.ServicePort = spec.ServicePort + project.Spec.Language = spec.Language + return project, r.Update(ctx, project) +} + +func (r *GroupReconciler) updateStatus(ctx context.Context, id int, group *gitlabv1alpha1.Group) error { + if id != 0 { + id64 := int64(id) + group.Status.GitlabID = &id64 + group.Status.LastUpdatedTime = metav1.Time{ + Time: time.Now(), + } + } + + return r.Status().Update(ctx, group) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *GroupReconciler) SetupWithManager(mgr ctrl.Manager) error { + // Setup a default GitlabClient implementation + r.gitlabClient = &ClientImpl{} + + return ctrl.NewControllerManagedBy(mgr). + For(&gitlabv1alpha1.Group{}). + Owns(&gitlabv1alpha1.Project{}). + Complete(r) } \ No newline at end of file diff --git a/controllers/gitlab/group_controller_test.go b/controllers/gitlab/group_controller_test.go index 3dd2024..c404b27 100755 --- a/controllers/gitlab/group_controller_test.go +++ b/controllers/gitlab/group_controller_test.go @@ -1,1156 +1,1156 @@ -package gitlab - -import ( - "context" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/xanzy/go-gitlab" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "net/http" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" -) - - -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("it looks up the API Object", func() { - Context("the API Object isn't found", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch group")) - }) - It("should return an empty result, and ignore the error.", func() { - Expect(result).Should(Equal(ctrl.Result{})) - Expect(err).Should(BeNil()) - }) - }) - }) -}) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("it looks up the GitlabCredentals", func() { - Context("gitlab credentials are not found", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - clientMock.GetFunction = nil - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{}, - Status: gitlabv1alpha1.GroupStatus{}, - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - result, err := sut.Reconcile(contextMock, requestMock) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch gitlab credentials")) - }) - It("should return a reconcile result, and the error.", func() { - Expect(result).To(Equal(ctrl.Result{ - Requeue: true, - })) - Expect(err).ToNot(BeNil()) - }) - }) - }) - }) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("getting the secret from the GitlabCredentials", func() { - Context("Secret doesn't exist", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - result, err := sut.Reconcile(contextMock, requestMock) - It("Should log an error regarding the missing credentials", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch secret from gitlab credentials")) - }) - It("Should return a requeue result and an error", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - Expect(err).ToNot(BeNil()) - }) - }) - }) - }) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("logging in to GitLab", func() { - Context("can't create client", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return nil, &MockError{ - message: "mocked error", - } - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("Should log the failure", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorUnableToCreateGitlabClient)) - }) - It("Should return an error", func() { - Expect(err).ToNot(BeNil()) - }) - It("should requeue the group", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - }) - }) - }) - -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("groupExists fails", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, &MockError{ - message: "Error", - } - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorWhileSearchingGroups)) - }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) - }) - }) - }) - -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("createGroup fails", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - groups := []*gitlab.Group{ &gitlab.Group{ - ID: 0, - Name: "", - Path: "", - Description: "", - MembershipLock: false, - Visibility: "", - LFSEnabled: false, - AvatarURL: "", - WebURL: "", - RequestAccessEnabled: false, - FullName: "", - FullPath: "", - ParentID: 0, - Projects: nil, - Statistics: nil, - CustomAttributes: nil, - ShareWithGroupLock: false, - RequireTwoFactorAuth: false, - TwoFactorGracePeriod: 0, - ProjectCreationLevel: "", - AutoDevopsEnabled: false, - SubGroupCreationLevel: "", - EmailsDisabled: false, - MentionsDisabled: false, - RunnersToken: "", - SharedProjects: nil, - SharedWithGroups: nil, - LDAPCN: "", - LDAPAccess: 0, - LDAPGroupLinks: nil, - SharedRunnersMinutesLimit: 0, - ExtraSharedRunnersMinutesLimit: 0, - MarkedForDeletionOn: nil, - CreatedAt: nil, - } } - return groups, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, nil - }, - createGroupFunction: func(name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{}, &MockError{ - message: "Error", - } - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorWhileCreatingGitlabGroup)) - }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) - }) - }) - }) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("updateGroup fails", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - groups := []*gitlab.Group{ {Name: "agroup"} } - return groups, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, nil - }, - updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{}, &MockError{ - message: "Error", - } - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorWhileUpdatingGitlabGroup)) - }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) - }) - }) - }) - -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("updateStatus fails", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ - statusWriter: &MockStatusWriter{ - updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return &MockError{ "error"} - }, - }, - } - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - groups := []*gitlab.Group{ {Name: "agroup"} } - return groups, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, nil - }, - updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return &gitlab.Group{ Name: "agroup" }, &gitlab.Response{}, nil - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorUnableToUpdateStatus)) - }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) - }) - }) - }) - -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("processProjects fails", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ - statusWriter: &MockStatusWriter{ - updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }, - }, - createFunction: func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error{ - return &MockError{message: "error"} - }, - } - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: []gitlabv1alpha1.ProjectSpec{ { Name: "ProjectSpec"} }, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - groups := []*gitlab.Group{ {Name: "agroup"} } - return groups, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, nil - }, - updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return &gitlab.Group{ Name: "agroup" }, &gitlab.Response{}, nil - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorUnableToProcessProjects)) - }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) - }) - }) - }) - -var _ = - Describe("groupExists", func() { - When("the gitlab client fails", func() { - sut := GroupReconciler{ - gitlabClient: &MockGitlabClient{ - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{}, &MockError{ - message: "mockedError", - } - }, - }, - } - found, response, groups, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "An error"}}) - It("should return that it didn't find the group", func() { - Expect(found).To(Equal(false)) - }) - It("should return the gitlab response", func() { - Expect(response).ToNot(BeNil()) - }) - It("should return a nil group", func() { - Expect(groups).To(BeNil()) - }) - It("should return an error", func() { - Expect(err).ToNot(BeNil()) - }) - }) - When("the group is in the list", func() { - group := &gitlab.Group{ Name: "A group" } - returnGroups := []*gitlab.Group{ group } - sut := GroupReconciler{ - gitlabClient: &MockGitlabClient{ - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return returnGroups, &gitlab.Response{}, nil - }, - }, - } - - found, response, returnedGroup, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "A group"}}) - It("should return that it found the group", func() { - Expect(found).To(Equal(true)) - }) - It("should return the gitlab response", func() { - Expect(response).ToNot(BeNil()) - }) - It("should return list with the group", func() { - Expect(returnedGroup).To(Equal(group)) - }) - It("should not return an error", func() { - Expect(err).To(BeNil()) - }) - }) - When("the group is not in the list", func() { - sut := GroupReconciler{ - gitlabClient: &MockGitlabClient{ - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{}, nil - }, - }, - } - - found, response, groups, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "A group"}}) - It("should return that it did not find the group", func() { - Expect(found).To(Equal(false)) - }) - It("should return the gitlab response", func() { - Expect(response).ToNot(BeNil()) - }) - It("should return nil for the group list", func() { - Expect(groups).To(BeNil()) - }) - It("should not return an error", func() { - Expect(err).To(BeNil()) - }) - }) - }) -var _ = - Describe("createGroup", func() { - expectedGroup := &gitlab.Group{ ID: 1, Name: "A test group"} - expectedResponse := &gitlab.Response{ - Response: &http.Response{}, - TotalItems: 1, - TotalPages: 1, - ItemsPerPage: 10, - CurrentPage: 1, - NextPage: 0, - PreviousPage: 0, - } - sut := GroupReconciler{ - gitlabClient: &MockGitlabClient{ - createGroupFunction: func(name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return expectedGroup, expectedResponse, nil - }, - }, - } - group, response, err := sut.createGroup( - &gitlabv1alpha1.Group{ - ObjectMeta: ctrl.ObjectMeta{Annotations: make(map[string]string)}, - Spec: gitlabv1alpha1.GroupSpec{Name: "a group"}, - }) - It("should return the gitlab group created", func() { - Expect(group).To(Equal(expectedGroup)) - }) - It("should return the gitlab response", func(){ - Expect(response).To(Equal(expectedResponse)) - }) - It("should return the error created by the call, or nil", func() { - Expect(err).To(BeNil()) - }) - }) - -var _ = - Describe("updateGroup", func() { - expectedGroup := &gitlab.Group{ID: 2, Name: "A test group"} - expectedResponse := &gitlab.Response{ - Response: &http.Response{}, - TotalItems: 1, - TotalPages: 1, - ItemsPerPage: 10, - CurrentPage: 1, - NextPage: 0, - PreviousPage: 0, - } - sut := GroupReconciler{ - gitlabClient: &MockGitlabClient{ - updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return expectedGroup, expectedResponse, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return []*gitlab.Group{{Name: "a group"}}, &gitlab.Response{}, nil - }, - }, - } - group, response, err := sut.updateGroup( - &gitlabv1alpha1.Group{ - ObjectMeta: ctrl.ObjectMeta{Annotations: make(map[string]string)}, - Spec: gitlabv1alpha1.GroupSpec{Name: "a group"}, - Status: gitlabv1alpha1.GroupStatus{ - GitlabID: nil, - CreatedTime: metav1.Time{}, - LastUpdatedTime: metav1.Time{}, - State: "", - }, - }) - It("should return the gitlab group created", func() { - Expect(group).To(Equal(expectedGroup)) - }) - It("should return the gitlab response", func(){ - Expect(response).To(Equal(expectedResponse)) - }) - It("should return the error created by the call, or nil", func() { - Expect(err).To(BeNil()) - }) - }) - -var _ = - Describe("SetupWithManager", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - err := sut.SetupWithManager(&MockManager{ - Log: &loggerMock, - builder: builder, - }) - It("should setup the default gitlab client", func() { - Expect(sut.gitlabClient).To(BeAssignableToTypeOf(&ClientImpl{})) - }) - It("should return no error", func() { - Expect(err).To(BeNil()) - }) - }) - -var _ = Describe("updateStatus", func() { - When("group id is not 0", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - group := gitlabv1alpha1.Group{Status: gitlabv1alpha1.GroupStatus{ - GitlabID: nil, - CreatedTime: metav1.Time{}, - LastUpdatedTime: metav1.Time{}, - State: "", - }} - sut.updateStatus(&MockContext{}, 1, &group) - - It("should update the gitlab id", func() { - Expect(*group.Status.GitlabID).To(Equal(int64(1))) - }) - It("should update the last updated time", func() { - Expect(group.Status.LastUpdatedTime.Time).To(Not(BeNil())) - }) - }) -}) - -var _ = - Describe("updateProject", func() { - spec := gitlabv1alpha1.ProjectSpec{ - Name: "a project", - Path: "https://example.com.path", - ImpactLevel: "2", - StorageTypes: nil, - VirtualService: "default", - ServicePort: 8081, - Language: "golang", - } - project := gitlabv1alpha1.Project{Spec: gitlabv1alpha1.ProjectSpec{}} - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - mockError := &MockError{"true"} - clientMock := MockClient{ - statusWriter: MockStatusWriter{ - updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }, - }, - updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return mockError - }, - } - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - - returnedProject, err := sut.updateProject(context.TODO(), spec, &project) - It("should update the project from the project specification", func() { - Expect(project.Spec.Name).To(Equal(spec.Name)) - Expect(project.Spec.ServicePort).To(Equal(spec.ServicePort)) - Expect(project.Spec.Language).To(Equal(spec.Language)) - Expect(project.Spec.Path).To(Equal(spec.Path)) - Expect(project.Spec.VirtualService).To(Equal(spec.VirtualService)) - Expect(project.Spec.StorageTypes).To(Equal(spec.StorageTypes)) - Expect(project.Spec.ImpactLevel).To(Equal(spec.ImpactLevel)) - }) - It("should call the kubernetes client update function", func() { - Expect(clientMock.updateFunctionCalled).To(BeTrue()) - }) - It("should return the result of the update function", func() { - Expect(err).To(Equal(mockError)) - }) - It("should return the updated project", func() { - Expect(project).To(Equal(*returnedProject)) - }) +package gitlab + +import ( + "context" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/xanzy/go-gitlab" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "net/http" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" +) + + +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("it looks up the API Object", func() { + Context("the API Object isn't found", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch group")) + }) + It("should return an empty result, and ignore the error.", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).Should(BeNil()) + }) + }) + }) +}) +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("it looks up the GitlabCredentals", func() { + Context("gitlab credentials are not found", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + clientMock.GetFunction = nil + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{}, + Status: gitlabv1alpha1.GroupStatus{}, + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + result, err := sut.Reconcile(contextMock, requestMock) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch gitlab credentials")) + }) + It("should return a reconcile result, and the error.", func() { + Expect(result).To(Equal(ctrl.Result{ + Requeue: true, + })) + Expect(err).ToNot(BeNil()) + }) + }) + }) + }) +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("getting the secret from the GitlabCredentials", func() { + Context("Secret doesn't exist", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + result, err := sut.Reconcile(contextMock, requestMock) + It("Should log an error regarding the missing credentials", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch secret from gitlab credentials")) + }) + It("Should return a requeue result and an error", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + Expect(err).ToNot(BeNil()) + }) + }) + }) + }) +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("logging in to GitLab", func() { + Context("can't create client", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return nil, &MockError{ + message: "mocked error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("Should log the failure", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorUnableToCreateGitlabClient)) + }) + It("Should return an error", func() { + Expect(err).ToNot(BeNil()) + }) + It("should requeue the group", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + }) + }) + }) + +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("groupExists fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, &MockError{ + message: "Error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorWhileSearchingGroups)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) + }) + +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("createGroup fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{ &gitlab.Group{ + ID: 0, + Name: "", + Path: "", + Description: "", + MembershipLock: false, + Visibility: "", + LFSEnabled: false, + AvatarURL: "", + WebURL: "", + RequestAccessEnabled: false, + FullName: "", + FullPath: "", + ParentID: 0, + Projects: nil, + Statistics: nil, + CustomAttributes: nil, + ShareWithGroupLock: false, + RequireTwoFactorAuth: false, + TwoFactorGracePeriod: 0, + ProjectCreationLevel: "", + AutoDevopsEnabled: false, + SubGroupCreationLevel: "", + EmailsDisabled: false, + MentionsDisabled: false, + RunnersToken: "", + SharedProjects: nil, + SharedWithGroups: nil, + LDAPCN: "", + LDAPAccess: 0, + LDAPGroupLinks: nil, + SharedRunnersMinutesLimit: 0, + ExtraSharedRunnersMinutesLimit: 0, + MarkedForDeletionOn: nil, + CreatedAt: nil, + } } + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + createGroupFunction: func(name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, &MockError{ + message: "Error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorWhileCreatingGitlabGroup)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) + }) +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("updateGroup fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{ {Name: "agroup"} } + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, &MockError{ + message: "Error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorWhileUpdatingGitlabGroup)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) + }) + +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("updateStatus fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ + statusWriter: &MockStatusWriter{ + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return &MockError{ "error"} + }, + }, + } + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{ {Name: "agroup"} } + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return &gitlab.Group{ Name: "agroup" }, &gitlab.Response{}, nil + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorUnableToUpdateStatus)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) + }) + +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("processProjects fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ + statusWriter: &MockStatusWriter{ + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }, + }, + createFunction: func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error{ + return &MockError{message: "error"} + }, + } + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: []gitlabv1alpha1.ProjectSpec{ { Name: "ProjectSpec"} }, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{ {Name: "agroup"} } + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return &gitlab.Group{ Name: "agroup" }, &gitlab.Response{}, nil + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorUnableToProcessProjects)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) + }) + +var _ = + Describe("groupExists", func() { + When("the gitlab client fails", func() { + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, &MockError{ + message: "mockedError", + } + }, + }, + } + found, response, groups, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "An error"}}) + It("should return that it didn't find the group", func() { + Expect(found).To(Equal(false)) + }) + It("should return the gitlab response", func() { + Expect(response).ToNot(BeNil()) + }) + It("should return a nil group", func() { + Expect(groups).To(BeNil()) + }) + It("should return an error", func() { + Expect(err).ToNot(BeNil()) + }) + }) + When("the group is in the list", func() { + group := &gitlab.Group{ Name: "A group" } + returnGroups := []*gitlab.Group{ group } + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return returnGroups, &gitlab.Response{}, nil + }, + }, + } + + found, response, returnedGroup, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "A group"}}) + It("should return that it found the group", func() { + Expect(found).To(Equal(true)) + }) + It("should return the gitlab response", func() { + Expect(response).ToNot(BeNil()) + }) + It("should return list with the group", func() { + Expect(returnedGroup).To(Equal(group)) + }) + It("should not return an error", func() { + Expect(err).To(BeNil()) + }) + }) + When("the group is not in the list", func() { + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, nil + }, + }, + } + + found, response, groups, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "A group"}}) + It("should return that it did not find the group", func() { + Expect(found).To(Equal(false)) + }) + It("should return the gitlab response", func() { + Expect(response).ToNot(BeNil()) + }) + It("should return nil for the group list", func() { + Expect(groups).To(BeNil()) + }) + It("should not return an error", func() { + Expect(err).To(BeNil()) + }) + }) + }) +var _ = + Describe("createGroup", func() { + expectedGroup := &gitlab.Group{ ID: 1, Name: "A test group"} + expectedResponse := &gitlab.Response{ + Response: &http.Response{}, + TotalItems: 1, + TotalPages: 1, + ItemsPerPage: 10, + CurrentPage: 1, + NextPage: 0, + PreviousPage: 0, + } + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + createGroupFunction: func(name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return expectedGroup, expectedResponse, nil + }, + }, + } + group, response, err := sut.createGroup( + &gitlabv1alpha1.Group{ + ObjectMeta: ctrl.ObjectMeta{Annotations: make(map[string]string)}, + Spec: gitlabv1alpha1.GroupSpec{Name: "a group"}, + }) + It("should return the gitlab group created", func() { + Expect(group).To(Equal(expectedGroup)) + }) + It("should return the gitlab response", func(){ + Expect(response).To(Equal(expectedResponse)) + }) + It("should return the error created by the call, or nil", func() { + Expect(err).To(BeNil()) + }) + }) + +var _ = + Describe("updateGroup", func() { + expectedGroup := &gitlab.Group{ID: 2, Name: "A test group"} + expectedResponse := &gitlab.Response{ + Response: &http.Response{}, + TotalItems: 1, + TotalPages: 1, + ItemsPerPage: 10, + CurrentPage: 1, + NextPage: 0, + PreviousPage: 0, + } + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return expectedGroup, expectedResponse, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return []*gitlab.Group{{Name: "a group"}}, &gitlab.Response{}, nil + }, + }, + } + group, response, err := sut.updateGroup( + &gitlabv1alpha1.Group{ + ObjectMeta: ctrl.ObjectMeta{Annotations: make(map[string]string)}, + Spec: gitlabv1alpha1.GroupSpec{Name: "a group"}, + Status: gitlabv1alpha1.GroupStatus{ + GitlabID: nil, + CreatedTime: metav1.Time{}, + LastUpdatedTime: metav1.Time{}, + State: "", + }, + }) + It("should return the gitlab group created", func() { + Expect(group).To(Equal(expectedGroup)) + }) + It("should return the gitlab response", func(){ + Expect(response).To(Equal(expectedResponse)) + }) + It("should return the error created by the call, or nil", func() { + Expect(err).To(BeNil()) + }) + }) + +var _ = + Describe("SetupWithManager", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + err := sut.SetupWithManager(&MockManager{ + Log: &loggerMock, + builder: builder, + }) + It("should setup the default gitlab client", func() { + Expect(sut.gitlabClient).To(BeAssignableToTypeOf(&ClientImpl{})) + }) + It("should return no error", func() { + Expect(err).To(BeNil()) + }) + }) + +var _ = Describe("updateStatus", func() { + When("group id is not 0", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + group := gitlabv1alpha1.Group{Status: gitlabv1alpha1.GroupStatus{ + GitlabID: nil, + CreatedTime: metav1.Time{}, + LastUpdatedTime: metav1.Time{}, + State: "", + }} + sut.updateStatus(&MockContext{}, 1, &group) + + It("should update the gitlab id", func() { + Expect(*group.Status.GitlabID).To(Equal(int64(1))) + }) + It("should update the last updated time", func() { + Expect(group.Status.LastUpdatedTime.Time).To(Not(BeNil())) + }) + }) +}) + +var _ = + Describe("updateProject", func() { + spec := gitlabv1alpha1.ProjectSpec{ + Name: "a project", + Path: "https://example.com.path", + ImpactLevel: "2", + StorageTypes: nil, + VirtualService: "default", + ServicePort: 8081, + Language: "golang", + } + project := gitlabv1alpha1.Project{Spec: gitlabv1alpha1.ProjectSpec{}} + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + mockError := &MockError{"true"} + clientMock := MockClient{ + statusWriter: MockStatusWriter{ + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }, + }, + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return mockError + }, + } + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + + returnedProject, err := sut.updateProject(context.TODO(), spec, &project) + It("should update the project from the project specification", func() { + Expect(project.Spec.Name).To(Equal(spec.Name)) + Expect(project.Spec.ServicePort).To(Equal(spec.ServicePort)) + Expect(project.Spec.Language).To(Equal(spec.Language)) + Expect(project.Spec.Path).To(Equal(spec.Path)) + Expect(project.Spec.VirtualService).To(Equal(spec.VirtualService)) + Expect(project.Spec.StorageTypes).To(Equal(spec.StorageTypes)) + Expect(project.Spec.ImpactLevel).To(Equal(spec.ImpactLevel)) + }) + It("should call the kubernetes client update function", func() { + Expect(clientMock.updateFunctionCalled).To(BeTrue()) + }) + It("should return the result of the update function", func() { + Expect(err).To(Equal(mockError)) + }) + It("should return the updated project", func() { + Expect(project).To(Equal(*returnedProject)) + }) }) \ No newline at end of file -- GitLab From 8c00fc7e35dc848f023ebb239de42069674152a6 Mon Sep 17 00:00:00 2001 From: Jason van Brackel Date: Mon, 21 Jun 2021 09:05:41 -0400 Subject: [PATCH 13/15] feat: working on project controller --- apis/gitlab/v1alpha1/group_types.go | 4 ---- apis/gitlab/v1alpha1/project_types.go | 16 +++++++++------- controllers/gitlab/group_controller.go | 5 +++-- controllers/gitlab/group_controller_test.go | 3 ++- controllers/gitlab/project_controller_test.go | 6 ++++++ 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/apis/gitlab/v1alpha1/group_types.go b/apis/gitlab/v1alpha1/group_types.go index c2f1feb..7700f96 100644 --- a/apis/gitlab/v1alpha1/group_types.go +++ b/apis/gitlab/v1alpha1/group_types.go @@ -30,10 +30,6 @@ type GroupSpec struct { // +kubebuilder:validation:Optional Description string `json:"description"` - // Owner is the Gitlab Username for the group owner - // +kubebuilder:validation:Optional - Owner string `json:"owner"` - // GitlabCredentialsName is the name of the object in this namespace that contains authentication // information for logging into the GitlabCredentialsName string `json:"gitlabCredentialsName"` diff --git a/apis/gitlab/v1alpha1/project_types.go b/apis/gitlab/v1alpha1/project_types.go index e1ea244..c16307e 100644 --- a/apis/gitlab/v1alpha1/project_types.go +++ b/apis/gitlab/v1alpha1/project_types.go @@ -20,11 +20,12 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! -// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. - // ProjectSpec defines the desired state of Project type ProjectSpec struct { + // GroupID is the id of the GitLab Group id that owns this project + // +kubebuilder:validation:required + GroupID int64`json:"groupId"` + // Name is the name of this Project // +kubebuilder:validation:required Name string `json:"name"` @@ -36,22 +37,23 @@ type ProjectSpec struct { // ImpactLevel is the RMF Impact Level for this Project // +kubebuilder:validation:Enum=2;4;5;6 // +kubebuiler:validation:required - ImpactLevel string `json:"impact_level"` + ImpactLevel string `json:"impactLevel"` // StorageTypes is the storage types that will be added to the Project // +kubebuiler:validation:optional - StorageTypes []string `json:"storage_types:omitempty"` + StorageTypes []string `json:"storageTypes:omitempty"` // VirtualService is the type of virtual service that will be created for this Project // +kubebuilder:default="Default" - VirtualService string `json:"virtual_service"` + VirtualService string `json:"virtualService"` // ServicePort is the port for the virtual service for the application served by this Project - ServicePort int32 `json:"service_port,omitempty"` + ServicePort int32 `json:"servicePort,omitempty"` // Language is the programming language of the Project for this application. // +kubebuiler:validation:required Language string `json:"language"` //TODO: Consider a custom type / annotation + } // ProjectStatus defines the observed state of Project diff --git a/controllers/gitlab/group_controller.go b/controllers/gitlab/group_controller.go index 1917714..3930cc7 100644 --- a/controllers/gitlab/group_controller.go +++ b/controllers/gitlab/group_controller.go @@ -221,7 +221,7 @@ func (r *GroupReconciler) processProjects(ctx context.Context, group *gitlabv1al r.Log.Error(err, errorLookingUpProject, "name", projectSpec.Name) return err } else if foundProject { - if _, err := r.updateProject(ctx, projectSpec, project); err != nil { + if _, err := r.updateProject(ctx, 0, projectSpec, project); err != nil { r.Log.Error(err, errorUpdatingProject, "project", project) } @@ -281,7 +281,8 @@ func (r *GroupReconciler) createProject(ctx context.Context, group *gitlabv1alph }) } -func (r *GroupReconciler) updateProject(ctx context.Context, spec gitlabv1alpha1.ProjectSpec, project *gitlabv1alpha1.Project) (*gitlabv1alpha1.Project, error) { +func (r *GroupReconciler) updateProject(ctx context.Context, groupId int64, spec gitlabv1alpha1.ProjectSpec, project *gitlabv1alpha1.Project) (*gitlabv1alpha1.Project, error) { + project.Spec.GroupID = groupId project.Spec.Name = spec.Name project.Spec.Path = spec.Path project.Spec.ImpactLevel = spec.ImpactLevel diff --git a/controllers/gitlab/group_controller_test.go b/controllers/gitlab/group_controller_test.go index c404b27..cf3a9f6 100755 --- a/controllers/gitlab/group_controller_test.go +++ b/controllers/gitlab/group_controller_test.go @@ -1134,8 +1134,9 @@ var _ = Scheme: scheme, } - returnedProject, err := sut.updateProject(context.TODO(), spec, &project) + returnedProject, err := sut.updateProject(context.TODO(), 1, spec, &project) It("should update the project from the project specification", func() { + Expect(project.Spec.GroupID).To(Equal(1)) Expect(project.Spec.Name).To(Equal(spec.Name)) Expect(project.Spec.ServicePort).To(Equal(spec.ServicePort)) Expect(project.Spec.Language).To(Equal(spec.Language)) diff --git a/controllers/gitlab/project_controller_test.go b/controllers/gitlab/project_controller_test.go index 11dafb6..ec96fe2 100644 --- a/controllers/gitlab/project_controller_test.go +++ b/controllers/gitlab/project_controller_test.go @@ -187,4 +187,10 @@ var _ = Describe("getProject", func() { Expect(err).ToNot(BeNil()) }) }) +}) + +var _ = Describe("getGroup", func() { + Context("green state", func() { + + }) }) \ No newline at end of file -- GitLab From f807bd1bf3bb7253fb4ccc3789aa3d8e986e5e39 Mon Sep 17 00:00:00 2001 From: Jason van Brackel Date: Tue, 22 Jun 2021 13:14:01 -0400 Subject: [PATCH 14/15] fix: failing type test --- controllers/gitlab/group_controller_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/gitlab/group_controller_test.go b/controllers/gitlab/group_controller_test.go index cf3a9f6..63c2661 100755 --- a/controllers/gitlab/group_controller_test.go +++ b/controllers/gitlab/group_controller_test.go @@ -1136,7 +1136,7 @@ var _ = returnedProject, err := sut.updateProject(context.TODO(), 1, spec, &project) It("should update the project from the project specification", func() { - Expect(project.Spec.GroupID).To(Equal(1)) + Expect(project.Spec.GroupID).To(Equal(int64(1))) Expect(project.Spec.Name).To(Equal(spec.Name)) Expect(project.Spec.ServicePort).To(Equal(spec.ServicePort)) Expect(project.Spec.Language).To(Equal(spec.Language)) -- GitLab From 3f5947797dab0eaa9398e3f384772ae217e5f12f Mon Sep 17 00:00:00 2001 From: Jason van Brackel Date: Tue, 22 Jun 2021 13:47:01 -0400 Subject: [PATCH 15/15] fix: correct lint failures --- controllers/gitlab/group_controller.go | 4 ++-- controllers/gitlab/project_controller.go | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/controllers/gitlab/group_controller.go b/controllers/gitlab/group_controller.go index eec558d..844c27c 100644 --- a/controllers/gitlab/group_controller.go +++ b/controllers/gitlab/group_controller.go @@ -281,8 +281,8 @@ func (r *GroupReconciler) createProject(ctx context.Context, group *gitlabv1alph }) } -func (r *GroupReconciler) updateProject(ctx context.Context, groupId int64, spec gitlabv1alpha1.ProjectSpec, project *gitlabv1alpha1.Project) (*gitlabv1alpha1.Project, error) { - project.Spec.GroupID = groupId +func (r *GroupReconciler) updateProject(ctx context.Context, groupID int64, spec gitlabv1alpha1.ProjectSpec, project *gitlabv1alpha1.Project) (*gitlabv1alpha1.Project, error) { + project.Spec.GroupID = groupID project.Spec.Name = spec.Name project.Spec.Path = spec.Path project.Spec.ImpactLevel = spec.ImpactLevel diff --git a/controllers/gitlab/project_controller.go b/controllers/gitlab/project_controller.go index b08e564..d115ef8 100644 --- a/controllers/gitlab/project_controller.go +++ b/controllers/gitlab/project_controller.go @@ -52,6 +52,8 @@ type ProjectReconciler struct { const ( errorWhileLookingUpProject string = "error while looking up project" ) + +// Reconcile is the main reconcilation loop that will create/edit/delete Gitlab Projects. func (r *ProjectReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = r.Log.WithValues("project", req.NamespacedName) -- GitLab