diff --git a/PROJECT b/PROJECT index e3cfad4d7b4c578a43bd129ae32259d230bb4ebb..a4d553cf3744d9d9fac3225ad2ed0b65ed2f4ba8 100644 --- a/PROJECT +++ b/PROJECT @@ -122,6 +122,24 @@ 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: fortify + kind: FortifyCredential + path: valkyrie.dso.mil/valkyrie-api/apis/fortify/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: valkyrie.dso.mil + group: fortify + kind: FortifyPipelineConfiguration + path: valkyrie.dso.mil/valkyrie-api/apis/fortify/v1alpha1 + version: v1alpha1 - api: crdVersion: v1 namespaced: true @@ -131,4 +149,22 @@ resources: kind: GitlabCredentials path: valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: valkyrie.dso.mil + group: twistlock + kind: TwistlockPipelineConfiguration + path: valkyrie.dso.mil/valkyrie-api/apis/twistlock/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: valkyrie.dso.mil + group: twistlock + kind: TwistlockCredential + path: valkyrie.dso.mil/valkyrie-api/apis/twistlock/v1alpha1 + version: v1alpha1 version: "3" diff --git a/README.md b/README.md index 386bea0642891956b50e427d77d6572324276904..f15ad9d0680969cfe46011dd69d1ea90667ea64c 100644 --- a/README.md +++ b/README.md @@ -52,4 +52,81 @@ open coverage.html package integration ``` - run integration tests as follows - - `go test -v -tags "integration" ./integration-tests/...` \ No newline at end of file + - `go test -v -tags "integration" ./integration-tests/...` + +### Twistlock Controller Setup +1) Create a secret that holds the credential for Twistlock API +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: twistlock-secret +type: Opaque +data: + usr: eW91cl90b2tlbg== + pwd: eW91cl90b2tlbg== +``` +2) Create a TwistlockCredential resource +```yaml +apiVersion: twistlock.valkyrie.dso.mil/v1alpha1 +kind: TwistlockCredential +metadata: + name: twistlockcredential-sample +spec: + serverUrl: "https://twistlock.bigbang.dev" + usernameSecretRef: + name: twistlock-secret + key: usr + passwordSecretRef: + name: twistlock-secret + key: pwd +``` +3) Create a TwistlockPipelineConfiguration resource +```yaml +apiVersion: twistlock.valkyrie.dso.mil/v1alpha1 +kind: TwistlockPipelineConfiguration +metadata: + name: twistlockpipelineconfiguration-sample +spec: + repository: "platform-one/devops/hello-pipeline/react-world" + registryHostname: "registry.il2.dso.mil" + registryCredentialId: "Pipeline" + credentialName: twistlockcredential-sample +``` +4) Apply the resources created above to the k8s cluster + +### Fortify Controller Setup +1) Create a secret that holds the access token for Fortify API. Note: controller will delete the project created when the custom resource is deleted, thus the token given needs to have to delete project permission. CI Access Token doesn't have delete project permission. +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: fortify-secret +type: Opaque +data: + accessToken: eW91cl90b2tlbg== +``` +2) Create a FortifyCredential resource +```yaml +apiVersion: fortify.valkyrie.dso.mil/v1alpha1 +kind: FortifyCredential +metadata: + name: fortifycredential-sample +spec: + serverUrl: "https://fortify.il2.dso.mil" + accessTokenSecretRef: + name: fortify-secret + key: accessToken +``` +3) Create a FortifyPipelineConfiguration resource +```yaml +apiVersion: fortify.valkyrie.dso.mil/v1alpha1 +kind: FortifyPipelineConfiguration +metadata: + name: fortifypipelineconfiguration-sample +spec: + projectName: "Valkyrie7" + language: "javascript" + credentialName: fortifycredential-sample +``` +4) Apply the resources created above to the k8s cluster diff --git a/apis/fortify/v1alpha1/fortifycredential_scaffold_test.go b/apis/fortify/v1alpha1/fortifycredential_scaffold_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a38a8d7c56a93b51365f24be85bc4e94d16ecaba --- /dev/null +++ b/apis/fortify/v1alpha1/fortifycredential_scaffold_test.go @@ -0,0 +1,260 @@ +package v1alpha1 + +import ( + v1 "k8s.io/api/core/v1" + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// testVarsFortifyCredential - Reusable test variables +type testVarsFortifyCredential = struct { + testKind string + testAPIVersion string + testSpec string + testStatus string + + expectedKind string + expectedAPIVersion string + expectedSpec string + expectedStatus string + + testObject1 FortifyCredential + testObject2 FortifyCredential + + objectItems1 []FortifyCredential + objectList1 FortifyCredentialList + + objectItems2 []FortifyCredential + objectList2 FortifyCredentialList + + // leave scaffold Foo value for testing? + testObjectSpec1 FortifyCredentialSpec + testObjectSpec2 FortifyCredentialSpec + + // leave scaffold Foo value for testing? + testObjectStatus1 FortifyCredentialStatus + testObjectStatus2 FortifyCredentialStatus +} + +// initVarsFortifyCredential - intialize test variables +func initVarsFortifyCredential() testVarsFortifyCredential { + testVars := testVarsFortifyCredential{} + + 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 = FortifyCredential{TypeMeta: object1MetaType} + + var object2MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind2", APIVersion: "V99"} + testVars.testObject2 = FortifyCredential{TypeMeta: object2MetaType} + + var objectList1MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind_List", APIVersion: "V12"} + var objectItems1 []FortifyCredential = []FortifyCredential{testVars.testObject1, testVars.testObject2} + // test_object_list = FortifyCredentialList(objectList1MetaType,nil,object_items) + testVars.objectList1 = FortifyCredentialList{TypeMeta: objectList1MetaType, Items: objectItems1} + + var objectList2MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind_List", APIVersion: "V12"} + var objectItems2 []FortifyCredential = []FortifyCredential{testVars.testObject2} + // test_object_list = FortifyCredentialList(objectList1MetaType,nil,object_items) + testVars.objectList2 = FortifyCredentialList{TypeMeta: objectList2MetaType, Items: objectItems2} + + // leave scaffold Foo value for testing? + testVars.testObjectSpec1 = FortifyCredentialSpec{ + ServerURL: testVars.testSpec, + AccessTokenSecRef: v1.SecretKeySelector{ + Key: "token1", + }, + } + testVars.testObjectSpec2 = FortifyCredentialSpec{ + ServerURL: testVars.testSpec, + AccessTokenSecRef: v1.SecretKeySelector{ + Key: "token2", + }, + } + + return testVars +} + +// TestGroupVarsFortifyCredential - +func TestGroupVarsFortifyCredential(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") +} + +// TestTypesFortifyCredential - +func TestTypesFortifyCredential(t *testing.T) { + lTestVars := initVarsFortifyCredential() + want := lTestVars.expectedAPIVersion + got := lTestVars.testObject1.APIVersion + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +// TestDeepCopyDeepCopyFortifyCredential - +func TestDeepCopyDeepCopyFortifyCredential(t *testing.T) { + lTestVars := initVarsFortifyCredential() + + 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 *FortifyCredential = nil + var val = nilTestPtr.DeepCopyObject() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + + t.Log("Success") +} + +// TestDeepCopyDeepCopyIntoFortifyCredential - +func TestDeepCopyDeepCopyIntoFortifyCredential(t *testing.T) { + lTestVars := initVarsFortifyCredential() + + 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") +} + +// TestDeepCopyDeepCopyObjectFortifyCredential - +func TestDeepCopyDeepCopyObjectFortifyCredential(t *testing.T) { + lTestVars := initVarsFortifyCredential() + + newRuntimeObject := lTestVars.testObject1.DeepCopyObject() + newObject := newRuntimeObject.(*FortifyCredential) + got := newObject.APIVersion + want := lTestVars.expectedAPIVersion + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +// TestDeepCopyDeepCopyListFortifyCredential - +func TestDeepCopyDeepCopyListFortifyCredential(t *testing.T) { + lTestVars := initVarsFortifyCredential() + 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 *FortifyCredentialList = nil + var val = nilTestPtr.DeepCopy() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + t.Log("Success") +} + +// TestDeepCopyDeepCopyIntoListFortifyCredential - +func TestDeepCopyDeepCopyIntoListFortifyCredential(t *testing.T) { + lTestVars := initVarsFortifyCredential() + + 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") +} + +// TestDeepCopyDeepCopyListObjectFortifyCredential - +func TestDeepCopyDeepCopyListObjectFortifyCredential(t *testing.T) { + lTestVars := initVarsFortifyCredential() + + newRuntimeObject := lTestVars.objectList1.DeepCopyObject() + newObject := newRuntimeObject.(*FortifyCredentialList) + got := newObject.Items[0].APIVersion + want := lTestVars.expectedAPIVersion + if got != want { + t.Errorf("got %s want %s", got, want) + } + + var nilTestPtr *FortifyCredentialList = nil + var val = nilTestPtr.DeepCopyObject() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + + t.Log("Success") +} + +// TestDeepCopyDeepCopySpecFortifyCredential - +func TestDeepCopyDeepCopySpecFortifyCredential(t *testing.T) { + lTestVars := initVarsFortifyCredential() + + newObjectList := lTestVars.testObjectSpec1.DeepCopy() + + got := newObjectList.ServerURL + want := lTestVars.expectedSpec + + if got != want { + t.Errorf("got %s want %s", got, want) + } + + var nilTestPtr *FortifyCredentialSpec = nil + var val = nilTestPtr.DeepCopy() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + t.Log("Success") +} + +// TestDeepCopyDeepCopySpecIntoFortifyCredential - +func TestDeepCopyDeepCopySpecIntoFortifyCredential(t *testing.T) { + lTestVars := initVarsFortifyCredential() + + lTestVars.testObjectSpec1.DeepCopyInto(&lTestVars.testObjectSpec2) + + got := lTestVars.testObjectSpec2.ServerURL + want := lTestVars.expectedSpec + + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} diff --git a/apis/fortify/v1alpha1/fortifycredential_types.go b/apis/fortify/v1alpha1/fortifycredential_types.go new file mode 100644 index 0000000000000000000000000000000000000000..dcae0a1f4441aad0d145080224fc8279858c54a6 --- /dev/null +++ b/apis/fortify/v1alpha1/fortifycredential_types.go @@ -0,0 +1,67 @@ +/* +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" +) + +// 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. + +// FortifyCredentialSpec defines the desired state of FortifyCredential +type FortifyCredentialSpec struct { + // ServerURL is the url for the Fortify server. For example: https://fortify.il2.dso.mil + ServerURL string `json:"serverUrl,omitempty"` + + //AccessTokenSecRef is the Secret Key Ref to the secret containing the Fortify Access Token for the user. + //TODO: CI Access Token is only valid for 1 year plus it doesn't support delete operation. We might need to + //get an admin access token. Need to check how long admin token is valid for. might have to use username + pass + AccessTokenSecRef v1.SecretKeySelector `json:"accessTokenSecretRef,omitempty"` +} + +// FortifyCredentialStatus defines the observed state of FortifyCredential +type FortifyCredentialStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// FortifyCredential is the Schema for the fortifycredentials API +type FortifyCredential struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec FortifyCredentialSpec `json:"spec,omitempty"` + Status FortifyCredentialStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// FortifyCredentialList contains a list of FortifyCredential +type FortifyCredentialList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []FortifyCredential `json:"items"` +} + +func init() { + SchemeBuilder.Register(&FortifyCredential{}, &FortifyCredentialList{}) +} diff --git a/apis/gitlab/v1alpha1/fortifypipelineconfiguration_scaffold_test.go b/apis/fortify/v1alpha1/fortifypipelineconfiguration_scaffold_test.go similarity index 68% rename from apis/gitlab/v1alpha1/fortifypipelineconfiguration_scaffold_test.go rename to apis/fortify/v1alpha1/fortifypipelineconfiguration_scaffold_test.go index ac9a2e969e61a6fbbf919c4ba6b5d0a43fc31066..10939e2525a049373a513bc020a0f355ee7aa61a 100644 --- a/apis/gitlab/v1alpha1/fortifypipelineconfiguration_scaffold_test.go +++ b/apis/fortify/v1alpha1/fortifypipelineconfiguration_scaffold_test.go @@ -7,17 +7,17 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// Reusable test variables -type testVarsFortifyPipelineConfiguration = struct { +// testVarsFortifyPipelineConfiguration - Reusable test variables +type testVarsFortifyPipelineConfiguration struct { testKind string - testApiversion string + testAPIVersion string testSpec string - testStatus string + testStatus int expectedKind string - expectedApiversion string + expectedAPIVersion string expectedSpec string - expectedStatus string + expectedStatus int testObject1 FortifyPipelineConfiguration testObject2 FortifyPipelineConfiguration @@ -30,27 +30,26 @@ type testVarsFortifyPipelineConfiguration = struct { // leave scaffold Foo value for testing? testObjectSpec1 FortifyPipelineConfigurationSpec - testObjectSpec2 FortifyPipelineConfigurationSpec // leave scaffold Foo value for testing? testObjectStatus1 FortifyPipelineConfigurationStatus - testObjectStatus2 FortifyPipelineConfigurationStatus } +// initVarsFortifyPipelineConfiguration - intialize test variables func initVarsFortifyPipelineConfiguration() testVarsFortifyPipelineConfiguration { testVars := testVarsFortifyPipelineConfiguration{} testVars.testKind = "TestKind" - testVars.testApiversion = "v22" - testVars.testSpec = "test spec value" - testVars.testStatus = "test status value" + testVars.testAPIVersion = "v22" + testVars.testSpec = "valkyrie" + testVars.testStatus = 88 - testVars.expectedApiversion = testVars.testApiversion + 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} + var object1MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: testVars.testKind, APIVersion: testVars.testAPIVersion} testVars.testObject1 = FortifyPipelineConfiguration{TypeMeta: object1MetaType} var object2MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind2", APIVersion: "V99"} @@ -67,17 +66,16 @@ func initVarsFortifyPipelineConfiguration() testVarsFortifyPipelineConfiguration testVars.objectList2 = FortifyPipelineConfigurationList{TypeMeta: objectList2MetaType, Items: objectItems2} // leave scaffold Foo value for testing? - testVars.testObjectSpec1 = FortifyPipelineConfigurationSpec{Dummy: testVars.testSpec} - testVars.testObjectSpec2 = FortifyPipelineConfigurationSpec{Dummy: "other value"} + testVars.testObjectSpec1 = FortifyPipelineConfigurationSpec{ProjectName: testVars.testSpec} // leave scaffold Foo value for testing? - testVars.testObjectStatus1 = FortifyPipelineConfigurationStatus{Dummy: testVars.testStatus} - testVars.testObjectStatus2 = FortifyPipelineConfigurationStatus{Dummy: "other value"} + testVars.testObjectStatus1 = FortifyPipelineConfigurationStatus{ProjectID: &testVars.testStatus} return testVars } -func TestGroupVars_FortifyPipelineConfiguration(t *testing.T) { +// TestGroupVarsFortifyPipelineConfiguration - +func TestGroupVarsFortifyPipelineConfiguration(t *testing.T) { xType := reflect.TypeOf(GroupVersion) // convert object type to string @@ -90,11 +88,10 @@ func TestGroupVars_FortifyPipelineConfiguration(t *testing.T) { t.Log("Success") } -// Test Type called FortifyPipelineConfiguration -func TestTypes_FortifyPipelineConfiguration(t *testing.T) { +// TestTypesFortifyPipelineConfiguration - +func TestTypesFortifyPipelineConfiguration(t *testing.T) { lTestVars := initVarsFortifyPipelineConfiguration() - want := lTestVars.expectedApiversion - + want := lTestVars.expectedAPIVersion got := lTestVars.testObject1.APIVersion if got != want { t.Errorf("got %s want %s", got, want) @@ -102,16 +99,15 @@ func TestTypes_FortifyPipelineConfiguration(t *testing.T) { t.Log("Success") } -// DeepCopy -func TestDeepCopy_DeepCopy_FortifyPipelineConfiguration(t *testing.T) { +// TestDeepCopyDeepCopyFortifyPipelineConfiguration - +func TestDeepCopyDeepCopyFortifyPipelineConfiguration(t *testing.T) { lTestVars := initVarsFortifyPipelineConfiguration() newObject := lTestVars.testObject1.DeepCopy() // check api version got := newObject.APIVersion - want := lTestVars.expectedApiversion - + want := lTestVars.expectedAPIVersion if got != want { t.Errorf("got %s want %s", got, want) } @@ -133,41 +129,41 @@ func TestDeepCopy_DeepCopy_FortifyPipelineConfiguration(t *testing.T) { t.Log("Success") } -func TestDeepCopy_DeepCopyInto_FortifyPipelineConfiguration(t *testing.T) { +// TestDeepCopyDeepCopyIntoFortifyPipelineConfiguration - +func TestDeepCopyDeepCopyIntoFortifyPipelineConfiguration(t *testing.T) { lTestVars := initVarsFortifyPipelineConfiguration() lTestVars.testObject1.DeepCopyInto(&lTestVars.testObject2) got := lTestVars.testObject2.APIVersion - want := lTestVars.expectedApiversion - + want := lTestVars.expectedAPIVersion if got != want { t.Errorf("got %s want %s", got, want) } t.Log("Success") } -func TestDeepCopy_DeepCopyObject_FortifyPipelineConfiguration(t *testing.T) { +// TestDeepCopyDeepCopyObjectFortifyPipelineConfiguration - +func TestDeepCopyDeepCopyObjectFortifyPipelineConfiguration(t *testing.T) { lTestVars := initVarsFortifyPipelineConfiguration() newRuntimeObject := lTestVars.testObject1.DeepCopyObject() newObject := newRuntimeObject.(*FortifyPipelineConfiguration) got := newObject.APIVersion - want := lTestVars.expectedApiversion - + want := lTestVars.expectedAPIVersion if got != want { t.Errorf("got %s want %s", got, want) } t.Log("Success") } -func TestDeepCopy_DeepCopyList_FortifyPipelineConfiguration(t *testing.T) { +// TestDeepCopyDeepCopyListFortifyPipelineConfiguration - +func TestDeepCopyDeepCopyListFortifyPipelineConfiguration(t *testing.T) { lTestVars := initVarsFortifyPipelineConfiguration() newObjectList := lTestVars.objectList1.DeepCopy() got := newObjectList.Items[0].APIVersion - want := lTestVars.expectedApiversion - + want := lTestVars.expectedAPIVersion if got != want { t.Errorf("got %s want %s", got, want) } @@ -181,28 +177,28 @@ func TestDeepCopy_DeepCopyList_FortifyPipelineConfiguration(t *testing.T) { t.Log("Success") } -func TestDeepCopy_DeepCopyIntoList_FortifyPipelineConfiguration(t *testing.T) { +// TestDeepCopyDeepCopyIntoListFortifyPipelineConfiguration - +func TestDeepCopyDeepCopyIntoListFortifyPipelineConfiguration(t *testing.T) { lTestVars := initVarsFortifyPipelineConfiguration() lTestVars.objectList1.DeepCopyInto(&lTestVars.objectList2) got := lTestVars.objectList2.Items[0].APIVersion - want := lTestVars.expectedApiversion - + want := lTestVars.expectedAPIVersion if got != want { t.Errorf("got %s want %s", got, want) } t.Log("Success") } -func TestDeepCopy_DeepCopyListObject_FortifyPipelineConfiguration(t *testing.T) { +// TestDeepCopyDeepCopyListObjectFortifyPipelineConfiguration - +func TestDeepCopyDeepCopyListObjectFortifyPipelineConfiguration(t *testing.T) { lTestVars := initVarsFortifyPipelineConfiguration() newRuntimeObject := lTestVars.objectList1.DeepCopyObject() newObject := newRuntimeObject.(*FortifyPipelineConfigurationList) got := newObject.Items[0].APIVersion - want := lTestVars.expectedApiversion - + want := lTestVars.expectedAPIVersion if got != want { t.Errorf("got %s want %s", got, want) } @@ -216,12 +212,13 @@ func TestDeepCopy_DeepCopyListObject_FortifyPipelineConfiguration(t *testing.T) t.Log("Success") } -func TestDeepCopy_DeepCopySpec_FortifyPipelineConfiguration(t *testing.T) { +// TestDeepCopyDeepCopySpecFortifyPipelineConfiguration - +func TestDeepCopyDeepCopySpecFortifyPipelineConfiguration(t *testing.T) { lTestVars := initVarsFortifyPipelineConfiguration() newObjectList := lTestVars.testObjectSpec1.DeepCopy() - got := newObjectList.Dummy + got := newObjectList.ProjectName want := lTestVars.expectedSpec if got != want { @@ -236,30 +233,17 @@ func TestDeepCopy_DeepCopySpec_FortifyPipelineConfiguration(t *testing.T) { t.Log("Success") } -func TestDeepCopy_DeepCopySpecInto_FortifyPipelineConfiguration(t *testing.T) { - lTestVars := initVarsFortifyPipelineConfiguration() - - lTestVars.testObjectSpec1.DeepCopyInto(&lTestVars.testObjectSpec2) - - got := lTestVars.testObjectSpec2.Dummy - want := lTestVars.expectedSpec - - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} - -func TestDeepCopy_DeepCopyStatus_FortifyPipelineConfiguration(t *testing.T) { +// TestDeepCopyDeepCopyStatusFortifyPipelineConfiguration - +func TestDeepCopyDeepCopyStatusFortifyPipelineConfiguration(t *testing.T) { lTestVars := initVarsFortifyPipelineConfiguration() newObjectStatus := lTestVars.testObjectStatus1.DeepCopy() - got := newObjectStatus.Dummy + got := *newObjectStatus.ProjectID want := lTestVars.expectedStatus if got != want { - t.Errorf("got %s want %s", got, want) + t.Errorf("got %d want %d", got, want) } // a typed pointer set to nil @@ -271,17 +255,3 @@ func TestDeepCopy_DeepCopyStatus_FortifyPipelineConfiguration(t *testing.T) { t.Log("Success") } - -func TestDeepCopy_DeepCopyStatusInto_FortifyPipelineConfiguration(t *testing.T) { - lTestVars := initVarsFortifyPipelineConfiguration() - - lTestVars.testObjectStatus1.DeepCopyInto(&lTestVars.testObjectStatus2) - - got := lTestVars.testObjectStatus2.Dummy - want := lTestVars.expectedStatus - - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} diff --git a/apis/gitlab/v1alpha1/fortifypipelineconfiguration_types.go b/apis/fortify/v1alpha1/fortifypipelineconfiguration_types.go similarity index 65% rename from apis/gitlab/v1alpha1/fortifypipelineconfiguration_types.go rename to apis/fortify/v1alpha1/fortifypipelineconfiguration_types.go index 79f8d26837dbf5f15fde020c9108adee00caab26..7a7aa347f567b0a6f4dc12611ae2eddd66420d0a 100644 --- a/apis/gitlab/v1alpha1/fortifypipelineconfiguration_types.go +++ b/apis/fortify/v1alpha1/fortifypipelineconfiguration_types.go @@ -25,20 +25,34 @@ import ( // FortifyPipelineConfigurationSpec defines the desired state of FortifyPipelineConfiguration type FortifyPipelineConfigurationSpec struct { - // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - // Important: Run "make" to regenerate code after modifying this file + //ProjectName is name of the fortify project, for example: platform-one-devops-hello-pipeline-react-world + ProjectName string `json:"projectName"` - // test Dummy is an example field of Project. Edit project_types.go to remove/update - Dummy string `json:"dummy,omitempty"` + //Language is the language that the project uses, for example: java, javascript etc + Language string `json:"language"` + + //FortifyCredentialName is the name of the Kind FortifyCredential object in this namespace that contains authentication information. + FortifyCredentialName string `json:"credentialName"` } // FortifyPipelineConfigurationStatus defines the observed state of FortifyPipelineConfiguration type FortifyPipelineConfigurationStatus 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"` + //ProjectID is the ID of the project in Fortify + // +optional + ProjectID *int `json:"projectID"` + + //CreatedTime is the timestamp when the project was created by the controller. If the project already exists beforehand, + //this will not be set. + // +optional + CreatedTime *metav1.Time `json:"createdTime"` + + //LastUpdatedTime is the latest timestamp when the project was updated by the controller. + // +optional + LastUpdatedTime *metav1.Time `json:"lastUpdatedTime"` + + //State is the current state + // +optional + State string `json:"state"` } //+kubebuilder:object:root=true diff --git a/apis/fortify/v1alpha1/groupversion_info.go b/apis/fortify/v1alpha1/groupversion_info.go new file mode 100644 index 0000000000000000000000000000000000000000..e3415cd8b7b2e41b059f2e4dcb08a25cfb9254c0 --- /dev/null +++ b/apis/fortify/v1alpha1/groupversion_info.go @@ -0,0 +1,36 @@ +/* +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 contains API Schema definitions for the fortify v1alpha1 API group +//+kubebuilder:object:generate=true +//+groupName=fortify.valkyrie.dso.mil +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "fortify.valkyrie.dso.mil", Version: "v1alpha1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/apis/fortify/v1alpha1/zz_generated.deepcopy.go b/apis/fortify/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000000000000000000000000000000000000..b4a45436cd4acecbce29369b29f68b14f5ecd8a1 --- /dev/null +++ b/apis/fortify/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,217 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FortifyCredential) DeepCopyInto(out *FortifyCredential) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FortifyCredential. +func (in *FortifyCredential) DeepCopy() *FortifyCredential { + if in == nil { + return nil + } + out := new(FortifyCredential) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FortifyCredential) 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 *FortifyCredentialList) DeepCopyInto(out *FortifyCredentialList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]FortifyCredential, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FortifyCredentialList. +func (in *FortifyCredentialList) DeepCopy() *FortifyCredentialList { + if in == nil { + return nil + } + out := new(FortifyCredentialList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FortifyCredentialList) 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 *FortifyCredentialSpec) DeepCopyInto(out *FortifyCredentialSpec) { + *out = *in + in.AccessTokenSecRef.DeepCopyInto(&out.AccessTokenSecRef) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FortifyCredentialSpec. +func (in *FortifyCredentialSpec) DeepCopy() *FortifyCredentialSpec { + if in == nil { + return nil + } + out := new(FortifyCredentialSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FortifyCredentialStatus) DeepCopyInto(out *FortifyCredentialStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FortifyCredentialStatus. +func (in *FortifyCredentialStatus) DeepCopy() *FortifyCredentialStatus { + if in == nil { + return nil + } + out := new(FortifyCredentialStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FortifyPipelineConfiguration) DeepCopyInto(out *FortifyPipelineConfiguration) { + *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 FortifyPipelineConfiguration. +func (in *FortifyPipelineConfiguration) DeepCopy() *FortifyPipelineConfiguration { + if in == nil { + return nil + } + out := new(FortifyPipelineConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FortifyPipelineConfiguration) 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 *FortifyPipelineConfigurationList) DeepCopyInto(out *FortifyPipelineConfigurationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]FortifyPipelineConfiguration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FortifyPipelineConfigurationList. +func (in *FortifyPipelineConfigurationList) DeepCopy() *FortifyPipelineConfigurationList { + if in == nil { + return nil + } + out := new(FortifyPipelineConfigurationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FortifyPipelineConfigurationList) 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 *FortifyPipelineConfigurationSpec) DeepCopyInto(out *FortifyPipelineConfigurationSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FortifyPipelineConfigurationSpec. +func (in *FortifyPipelineConfigurationSpec) DeepCopy() *FortifyPipelineConfigurationSpec { + if in == nil { + return nil + } + out := new(FortifyPipelineConfigurationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FortifyPipelineConfigurationStatus) DeepCopyInto(out *FortifyPipelineConfigurationStatus) { + *out = *in + if in.ProjectID != nil { + in, out := &in.ProjectID, &out.ProjectID + *out = new(int) + **out = **in + } + if in.CreatedTime != nil { + in, out := &in.CreatedTime, &out.CreatedTime + *out = (*in).DeepCopy() + } + if in.LastUpdatedTime != nil { + in, out := &in.LastUpdatedTime, &out.LastUpdatedTime + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FortifyPipelineConfigurationStatus. +func (in *FortifyPipelineConfigurationStatus) DeepCopy() *FortifyPipelineConfigurationStatus { + if in == nil { + return nil + } + out := new(FortifyPipelineConfigurationStatus) + in.DeepCopyInto(out) + return out +} diff --git a/apis/gitlab/v1alpha1/zz_generated.deepcopy.go b/apis/gitlab/v1alpha1/zz_generated.deepcopy.go index 89108db129d5385790e57349a58fbbec71a482fb..fc6171901ba1cac9cf66fc07143a7e43254e7878 100644 --- a/apis/gitlab/v1alpha1/zz_generated.deepcopy.go +++ b/apis/gitlab/v1alpha1/zz_generated.deepcopy.go @@ -24,95 +24,6 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FortifyPipelineConfiguration) DeepCopyInto(out *FortifyPipelineConfiguration) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - out.Status = in.Status -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FortifyPipelineConfiguration. -func (in *FortifyPipelineConfiguration) DeepCopy() *FortifyPipelineConfiguration { - if in == nil { - return nil - } - out := new(FortifyPipelineConfiguration) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *FortifyPipelineConfiguration) 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 *FortifyPipelineConfigurationList) DeepCopyInto(out *FortifyPipelineConfigurationList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]FortifyPipelineConfiguration, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FortifyPipelineConfigurationList. -func (in *FortifyPipelineConfigurationList) DeepCopy() *FortifyPipelineConfigurationList { - if in == nil { - return nil - } - out := new(FortifyPipelineConfigurationList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *FortifyPipelineConfigurationList) 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 *FortifyPipelineConfigurationSpec) DeepCopyInto(out *FortifyPipelineConfigurationSpec) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FortifyPipelineConfigurationSpec. -func (in *FortifyPipelineConfigurationSpec) DeepCopy() *FortifyPipelineConfigurationSpec { - if in == nil { - return nil - } - out := new(FortifyPipelineConfigurationSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FortifyPipelineConfigurationStatus) DeepCopyInto(out *FortifyPipelineConfigurationStatus) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FortifyPipelineConfigurationStatus. -func (in *FortifyPipelineConfigurationStatus) DeepCopy() *FortifyPipelineConfigurationStatus { - if in == nil { - return nil - } - out := new(FortifyPipelineConfigurationStatus) - in.DeepCopyInto(out) - 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 @@ -667,92 +578,3 @@ func (in *SonarqubePipelineConfigurationStatus) DeepCopy() *SonarqubePipelineCon in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TwistlockPipelineConfiguration) DeepCopyInto(out *TwistlockPipelineConfiguration) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - out.Status = in.Status -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TwistlockPipelineConfiguration. -func (in *TwistlockPipelineConfiguration) DeepCopy() *TwistlockPipelineConfiguration { - if in == nil { - return nil - } - out := new(TwistlockPipelineConfiguration) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TwistlockPipelineConfiguration) 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 *TwistlockPipelineConfigurationList) DeepCopyInto(out *TwistlockPipelineConfigurationList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]TwistlockPipelineConfiguration, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TwistlockPipelineConfigurationList. -func (in *TwistlockPipelineConfigurationList) DeepCopy() *TwistlockPipelineConfigurationList { - if in == nil { - return nil - } - out := new(TwistlockPipelineConfigurationList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TwistlockPipelineConfigurationList) 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 *TwistlockPipelineConfigurationSpec) DeepCopyInto(out *TwistlockPipelineConfigurationSpec) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TwistlockPipelineConfigurationSpec. -func (in *TwistlockPipelineConfigurationSpec) DeepCopy() *TwistlockPipelineConfigurationSpec { - if in == nil { - return nil - } - out := new(TwistlockPipelineConfigurationSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TwistlockPipelineConfigurationStatus) DeepCopyInto(out *TwistlockPipelineConfigurationStatus) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TwistlockPipelineConfigurationStatus. -func (in *TwistlockPipelineConfigurationStatus) DeepCopy() *TwistlockPipelineConfigurationStatus { - if in == nil { - return nil - } - out := new(TwistlockPipelineConfigurationStatus) - in.DeepCopyInto(out) - return out -} diff --git a/apis/twistlock/v1alpha1/groupversion_info.go b/apis/twistlock/v1alpha1/groupversion_info.go new file mode 100644 index 0000000000000000000000000000000000000000..0f160f9860c80d0e982cb99d7563c2dee2616a97 --- /dev/null +++ b/apis/twistlock/v1alpha1/groupversion_info.go @@ -0,0 +1,36 @@ +/* +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 contains API Schema definitions for the twistlock v1alpha1 API group +//+kubebuilder:object:generate=true +//+groupName=twistlock.valkyrie.dso.mil +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "twistlock.valkyrie.dso.mil", Version: "v1alpha1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/apis/twistlock/v1alpha1/twistlockcredential_types.go b/apis/twistlock/v1alpha1/twistlockcredential_types.go new file mode 100644 index 0000000000000000000000000000000000000000..6c5b61808bda4c40ca8b8019efc823a499d223f4 --- /dev/null +++ b/apis/twistlock/v1alpha1/twistlockcredential_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" +) + +// 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. + +// TwistlockCredentialSpec defines the desired state of TwistlockCredential +type TwistlockCredentialSpec struct { + // ServerURL is the url for the Fortify server. For example: https://fortify.il2.dso.mil + ServerURL string `json:"serverUrl"` + + // UsernameSecRef is the username secret used for API calls to Twistlock + UsernameSecRef v1.SecretKeySelector `json:"usernameSecretRef"` + + // PasswordSecRef is the password secret used for API calls to Twistlock + PasswordSecRef v1.SecretKeySelector `json:"passwordSecretRef"` +} + +// TwistlockCredentialStatus defines the observed state of TwistlockCredential +type TwistlockCredentialStatus struct { +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// TwistlockCredential is the Schema for the twistlockcredential API +type TwistlockCredential struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TwistlockCredentialSpec `json:"spec,omitempty"` + Status TwistlockCredentialStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// TwistlockCredentialList contains a list of TwistlockCredential +type TwistlockCredentialList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []TwistlockCredential `json:"items"` +} + +func init() { + SchemeBuilder.Register(&TwistlockCredential{}, &TwistlockCredentialList{}) +} diff --git a/apis/gitlab/v1alpha1/twistlockpipelineconfiguration_scaffold_test.go b/apis/twistlock/v1alpha1/twistlockpipelineconfiguration_scaffold_test.go similarity index 98% rename from apis/gitlab/v1alpha1/twistlockpipelineconfiguration_scaffold_test.go rename to apis/twistlock/v1alpha1/twistlockpipelineconfiguration_scaffold_test.go index 8158f881cdb53556672feb807d477352708d2073..8ea0e8d94a94b0bfaf5c7bd9e4dfb0bf74b25068 100644 --- a/apis/gitlab/v1alpha1/twistlockpipelineconfiguration_scaffold_test.go +++ b/apis/twistlock/v1alpha1/twistlockpipelineconfiguration_scaffold_test.go @@ -71,8 +71,8 @@ func initVarsTwistlockPipelineConfiguration() testVarsTwistlockPipelineConfigura testVars.testObjectSpec2 = TwistlockPipelineConfigurationSpec{Repository: "other value"} // leave scaffold Foo value for testing? - testVars.testObjectStatus1 = TwistlockPipelineConfigurationStatus{Dummy: testVars.testStatus} - testVars.testObjectStatus2 = TwistlockPipelineConfigurationStatus{Dummy: "other value"} + testVars.testObjectStatus1 = TwistlockPipelineConfigurationStatus{State: testVars.testStatus} + testVars.testObjectStatus2 = TwistlockPipelineConfigurationStatus{State: "other value"} return testVars } @@ -255,7 +255,7 @@ func TestDeepCopy_DeepCopyStatus_TwistlockPipelineConfiguration(t *testing.T) { newObjectStatus := lTestVars.testObjectStatus1.DeepCopy() - got := newObjectStatus.Dummy + got := newObjectStatus.State want := lTestVars.expectedStatus if got != want { @@ -277,7 +277,7 @@ func TestDeepCopy_DeepCopyStatusInto_TwistlockPipelineConfiguration(t *testing.T lTestVars.testObjectStatus1.DeepCopyInto(&lTestVars.testObjectStatus2) - got := lTestVars.testObjectStatus2.Dummy + got := lTestVars.testObjectStatus2.State want := lTestVars.expectedStatus if got != want { diff --git a/apis/gitlab/v1alpha1/twistlockpipelineconfiguration_types.go b/apis/twistlock/v1alpha1/twistlockpipelineconfiguration_types.go similarity index 67% rename from apis/gitlab/v1alpha1/twistlockpipelineconfiguration_types.go rename to apis/twistlock/v1alpha1/twistlockpipelineconfiguration_types.go index 31ff51df5da6f94a3579dd2ac6ed87d4c36e09f9..fb2aae7bbb0fd847b7cb4c64f7e962cc130e7813 100644 --- a/apis/gitlab/v1alpha1/twistlockpipelineconfiguration_types.go +++ b/apis/twistlock/v1alpha1/twistlockpipelineconfiguration_types.go @@ -20,28 +20,37 @@ 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. - // TwistlockPipelineConfigurationSpec defines the desired state of TwistlockPipelineConfiguration type TwistlockPipelineConfigurationSpec struct { - // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - // Important: Run "make" to regenerate code after modifying this file //Repository path for the gitlab repo, for example: platform-one/devops/hello-pipeline/react-world - Repository string `json:"repository,omitempty"` + Repository string `json:"repository"` + + //RegistryHostname is the registry used when creating project in Twistlock + RegistryHostname string `json:"registryHostname"` + + //RegistryCredentialID is the credential ID in Twistlock used to access RegistryHostname. It's expected that the credential already exists in Twistlock. + RegistryCredentialID string `json:"registryCredentialId"` - //RegistryCredentialID is the credential ID in Twistlock used to access the registry. It's expected that the credential already exists in Twistlock. - RegistryCredentialID string `json:"credentialId,omitempty"` + //CredentialName is the name of the Kind TwistlockCredential object in this namespace that contains authentication information. + CredentialName string `json:"credentialName"` } // TwistlockPipelineConfigurationStatus defines the observed state of TwistlockPipelineConfiguration type TwistlockPipelineConfigurationStatus 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"` + //CreatedTime is the timestamp when the project was created by the controller. If the project already exists beforehand, + //this will not be set. + // +optional + CreatedTime *metav1.Time `json:"createdTime,omitempty"` + + //LastUpdatedTime is the latest timestamp when the project was updated by the controller. + // +optional + LastUpdatedTime *metav1.Time `json:"lastUpdatedTime,omitempty"` + + //State is the current state + // +optional + State string `json:"state,omitempty"` } //+kubebuilder:object:root=true diff --git a/apis/twistlock/v1alpha1/zz_generated.deepcopy.go b/apis/twistlock/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000000000000000000000000000000000000..e5d05d434070c76d4b77e7facefadc362eff9fc3 --- /dev/null +++ b/apis/twistlock/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,213 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TwistlockCredential) DeepCopyInto(out *TwistlockCredential) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TwistlockCredential. +func (in *TwistlockCredential) DeepCopy() *TwistlockCredential { + if in == nil { + return nil + } + out := new(TwistlockCredential) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TwistlockCredential) 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 *TwistlockCredentialList) DeepCopyInto(out *TwistlockCredentialList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TwistlockCredential, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TwistlockCredentialList. +func (in *TwistlockCredentialList) DeepCopy() *TwistlockCredentialList { + if in == nil { + return nil + } + out := new(TwistlockCredentialList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TwistlockCredentialList) 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 *TwistlockCredentialSpec) DeepCopyInto(out *TwistlockCredentialSpec) { + *out = *in + in.UsernameSecRef.DeepCopyInto(&out.UsernameSecRef) + in.PasswordSecRef.DeepCopyInto(&out.PasswordSecRef) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TwistlockCredentialSpec. +func (in *TwistlockCredentialSpec) DeepCopy() *TwistlockCredentialSpec { + if in == nil { + return nil + } + out := new(TwistlockCredentialSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TwistlockCredentialStatus) DeepCopyInto(out *TwistlockCredentialStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TwistlockCredentialStatus. +func (in *TwistlockCredentialStatus) DeepCopy() *TwistlockCredentialStatus { + if in == nil { + return nil + } + out := new(TwistlockCredentialStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TwistlockPipelineConfiguration) DeepCopyInto(out *TwistlockPipelineConfiguration) { + *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 TwistlockPipelineConfiguration. +func (in *TwistlockPipelineConfiguration) DeepCopy() *TwistlockPipelineConfiguration { + if in == nil { + return nil + } + out := new(TwistlockPipelineConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TwistlockPipelineConfiguration) 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 *TwistlockPipelineConfigurationList) DeepCopyInto(out *TwistlockPipelineConfigurationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TwistlockPipelineConfiguration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TwistlockPipelineConfigurationList. +func (in *TwistlockPipelineConfigurationList) DeepCopy() *TwistlockPipelineConfigurationList { + if in == nil { + return nil + } + out := new(TwistlockPipelineConfigurationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TwistlockPipelineConfigurationList) 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 *TwistlockPipelineConfigurationSpec) DeepCopyInto(out *TwistlockPipelineConfigurationSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TwistlockPipelineConfigurationSpec. +func (in *TwistlockPipelineConfigurationSpec) DeepCopy() *TwistlockPipelineConfigurationSpec { + if in == nil { + return nil + } + out := new(TwistlockPipelineConfigurationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TwistlockPipelineConfigurationStatus) DeepCopyInto(out *TwistlockPipelineConfigurationStatus) { + *out = *in + if in.CreatedTime != nil { + in, out := &in.CreatedTime, &out.CreatedTime + *out = (*in).DeepCopy() + } + if in.LastUpdatedTime != nil { + in, out := &in.LastUpdatedTime, &out.LastUpdatedTime + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TwistlockPipelineConfigurationStatus. +func (in *TwistlockPipelineConfigurationStatus) DeepCopy() *TwistlockPipelineConfigurationStatus { + if in == nil { + return nil + } + out := new(TwistlockPipelineConfigurationStatus) + in.DeepCopyInto(out) + return out +} diff --git a/clients/fortify/client.go b/clients/fortify/client.go new file mode 100644 index 0000000000000000000000000000000000000000..5d831143becf0f4db228d200e07785c398c30cf7 --- /dev/null +++ b/clients/fortify/client.go @@ -0,0 +1,106 @@ +package fortify + +import ( + "encoding/json" + "net/http" + "strconv" + "valkyrie.dso.mil/valkyrie-api/clients" +) + +const apiVersion = "/api/v1" +const authHeaderPrefix = "FortifyToken " + +//Client is the API client for access Fortify +type Client struct { + apiToken string + //Example: https://fortify.il2.dso.mil/api/v1 + serverAPIURL string + httpClient *http.Client +} + +//NewClient creates a new API client for fortify +func NewClient(serverURL string, apiToken string) *Client { + return &Client{ + serverAPIURL: serverURL + apiVersion, + apiToken: apiToken, + httpClient: &http.Client{}, + } +} + +func getAuthHeader(token string) string { + return authHeaderPrefix + token +} + +//GetServerAPIBaseURL gets the base url for the API calls. Example return: https://fortify.il2.dso.mil/api/v1 +func (c Client) GetServerAPIBaseURL() string { + return c.serverAPIURL +} + +//SearchProjectVersions gets all the project versions based on the search input. By default, max 200 entries are returned. +//If want everything to be returned, set ResultLimit to 0 in ProjectSearch. +func (c Client) SearchProjectVersions(search *ProjectSearch) (projectVersions ProjectVersionsResponse, err error) { + + // make the request + var queryParams map[string]string + if search != nil { + queryParams = make(map[string]string, 2) + limit := strconv.Itoa(search.ResultLimit) + queryParams["limit"] = limit + if search.ProjectName != "" { + queryParams["q"] = "project.name:" + search.ProjectName + } + } + endpoint := c.serverAPIURL + "/projectVersions" + authHeaderValue := getAuthHeader(c.apiToken) + + var resp []byte + resp, err = clients.Get(c.httpClient, endpoint, authHeaderValue, queryParams, nil, 200) + if err != nil { + return + } + err = json.Unmarshal(resp, &projectVersions) + if err != nil { + return + } + return +} + +// CreateProjectVersion creates a new project version +func (c Client) CreateProjectVersion(request ProjectVersionCreateRequest) (projectVersion ProjectVersionCreateResponse, err error) { + endpoint := c.serverAPIURL + "/projectVersions" + authHeaderValue := getAuthHeader(c.apiToken) + + var resp []byte + resp, err = clients.Post(c.httpClient, endpoint, authHeaderValue, request, 201) + if err != nil { + return + } + err = json.Unmarshal(resp, &projectVersion) + if err != nil { + return + } + return +} + +// DeleteProjectVersion deletes the project version for the given project version ID +func (c Client) DeleteProjectVersion(projectVersionID int) (err error) { + endpoint := c.serverAPIURL + "/projectVersions/" + strconv.Itoa(projectVersionID) + authHeaderValue := getAuthHeader(c.apiToken) + _, err = clients.Delete(c.httpClient, endpoint, authHeaderValue, 200) + if err != nil { + return + } + return +} + +//BulkUpdate sends array of HTTP requests to the server to process all at once +func (c Client) BulkUpdate(request BulkUpdateRequest) (err error) { + endpoint := c.serverAPIURL + "/bulk" + authHeaderValue := getAuthHeader(c.apiToken) + + _, err = clients.Post(c.httpClient, endpoint, authHeaderValue, request, 200) + if err != nil { + return err + } + return +} diff --git a/clients/fortify/client_test.go b/clients/fortify/client_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b76c71cc93f679677a3e6b284034e6ab7ab48348 --- /dev/null +++ b/clients/fortify/client_test.go @@ -0,0 +1,262 @@ +package fortify + +import ( + "errors" + "github.com/jarcoal/httpmock" + "testing" +) + +func TestClient_GetServerAPIBaseURL(t *testing.T) { + client := NewClient("http://fortify.bigbang.dev", "token") + if client.GetServerAPIBaseURL() != "http://fortify.bigbang.dev/api/v1" { + t.Errorf("Unexpected server API base URL found: %s", client.GetServerAPIBaseURL()) + } +} + +func TestClient_SearchProjectVersions(t *testing.T) { + //testHTTPClient := &http.Client{} + + tests := []struct { + name string + want *ProjectVersionsResponse + wantErr bool + }{ + {name: "searchProject", want: &ProjectVersionsResponse{Data: []ProjectVersionResponse{{ + ID: 88, + Name: "Valkyrie", + Description: "Created By Valkyrie", + CreatedBy: "Valkyrie", + CreationDate: "", + }}}, wantErr: false}, + {name: "searchProject Error", want: nil, wantErr: true}, + } + + // mock http call + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + for _, tt := range tests { + + if tt.wantErr { + errorResponder := httpmock.NewErrorResponder(errors.New("Test Error")) + httpmock.RegisterResponder("GET", + "https://test/api/v1/projectVersions?limit=1&q=project.name%3AValkyrie", + errorResponder, + ) + + t.Run(tt.name, func(t *testing.T) { + r := NewClient("https://test", "token") + _, err := r.SearchProjectVersions(&ProjectSearch{ + ResultLimit: 1, + ProjectName: "Valkyrie", + }) + if err == nil { + t.Error("Expect error, but got nil") + } + }) + + } else { + httpmock.RegisterResponder("GET", + "https://test/api/v1/projectVersions?limit=1&q=project.name%3AValkyrie", + httpmock.NewJsonResponderOrPanic(200, tt.want), + ) + + t.Run(tt.name, func(t *testing.T) { + r := NewClient("https://test", "token") + got, err := r.SearchProjectVersions(&ProjectSearch{ + ResultLimit: 1, + ProjectName: "Valkyrie", + }) + if err != nil { + t.Error("Expect no error") + } + if len(got.Data) != 1 { + t.Error("Should only get back 1 result") + } + + if got.Data[0].ID != 88 { + t.Errorf("Unexpected project ID found") + } + }) + } + } +} + +func TestClient_CreateProjectVersion(t *testing.T) { + tests := []struct { + name string + want *ProjectVersionCreateResponse + wantErr bool + }{ + {name: "createProject", want: &ProjectVersionCreateResponse{Data: ProjectVersionResponse{ + ID: 88, + Name: "Valkyrie", + Description: "Created By Valkyrie", + CreatedBy: "Valkyrie", + CreationDate: "", + }}, wantErr: false}, + {name: "createProject Error", want: nil, wantErr: true}, + } + + // mock http call + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + for _, tt := range tests { + + if tt.wantErr { + errorResponder := httpmock.NewErrorResponder(errors.New("Test Error")) + httpmock.RegisterResponder("POST", + "https://test/api/v1/projectVersions", + errorResponder, + ) + + t.Run(tt.name, func(t *testing.T) { + r := NewClient("https://test", "token") + request := ProjectVersionCreateRequest{ + Name: "0.1", + Description: "Valkyrie Test", + Active: true, + Committed: false, + Project: ProjectRequest{ + Name: "Valkyrie3", + Description: "Created by Valkyrie", + IssueTemplateID: "Prioritized-HighRisk-Project-Template", + }, + IssueTemplateID: "Prioritized-HighRisk-Project-Template", + } + _, err := r.CreateProjectVersion(request) + if err == nil { + t.Error("Expect error, but got nil") + } + }) + + } else { + httpmock.RegisterResponder("POST", + "https://test/api/v1/projectVersions", + httpmock.NewJsonResponderOrPanic(201, tt.want), + ) + + t.Run(tt.name, func(t *testing.T) { + r := NewClient("https://test", "token") + request := ProjectVersionCreateRequest{ + Name: "0.1", + Description: "Valkyrie Test", + Active: true, + Committed: false, + Project: ProjectRequest{ + Name: "Valkyrie3", + Description: "Created by Valkyrie", + IssueTemplateID: "Prioritized-HighRisk-Project-Template", + }, + IssueTemplateID: "Prioritized-HighRisk-Project-Template", + } + got, err := r.CreateProjectVersion(request) + if err != nil { + t.Error("Expect no error") + } + if got.Data.ID != 88 { + t.Error("Unexpected project ID returned") + } + }) + } + } +} + +func TestClient_DeleteProjectVersion(t *testing.T) { + + tests := []struct { + name string + versionID int + wantErr bool + }{ + {name: "deleteProject", versionID: 88, wantErr: false}, + {name: "deleteProject Error", versionID: 88, wantErr: true}, + } + + // mock http call + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + for _, tt := range tests { + + if tt.wantErr { + errorResponder := httpmock.NewErrorResponder(errors.New("Test Error")) + httpmock.RegisterResponder("DELETE", + "https://test/api/v1/projectVersions/88", + errorResponder, + ) + + t.Run(tt.name, func(t *testing.T) { + r := NewClient("https://test", "token") + err := r.DeleteProjectVersion(88) + if err == nil { + t.Error("Expect error to be returned, found no error") + } + }) + + } else { + httpmock.RegisterResponder("DELETE", + "https://test/api/v1/projectVersions/88", + httpmock.NewJsonResponderOrPanic(200, nil), + ) + + t.Run(tt.name, func(t *testing.T) { + r := NewClient("https://test", "token") + err := r.DeleteProjectVersion(88) + if err != nil { + t.Error("Expect no error, but found error") + } + }) + } + } +} + +func TestClient_BulkUpdate(t *testing.T) { + + tests := []struct { + name string + versionID int + wantErr bool + }{ + {name: "bulkUpdate", versionID: 88, wantErr: false}, + {name: "bulkUpdate Error", versionID: 88, wantErr: true}, + } + + // mock http call + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + for _, tt := range tests { + + if tt.wantErr { + errorResponder := httpmock.NewErrorResponder(errors.New("Test Error")) + httpmock.RegisterResponder("POST", + "https://test/api/v1/bulk", + errorResponder, + ) + + t.Run(tt.name, func(t *testing.T) { + r := NewClient("https://test", "token") + err := r.BulkUpdate(BulkUpdateRequest{}) + if err == nil { + t.Error("Expect error to be returned, found no error") + } + }) + + } else { + httpmock.RegisterResponder("POST", + "https://test/api/v1/bulk", + httpmock.NewJsonResponderOrPanic(200, nil), + ) + + t.Run(tt.name, func(t *testing.T) { + r := NewClient("https://test", "token") + err := r.BulkUpdate(BulkUpdateRequest{}) + if err != nil { + t.Error("Expect no error, but found error") + } + }) + } + } +} diff --git a/clients/fortify/requests.go b/clients/fortify/requests.go new file mode 100644 index 0000000000000000000000000000000000000000..c3df37d33631f578837a8ac06c88de3558b68e28 --- /dev/null +++ b/clients/fortify/requests.go @@ -0,0 +1,54 @@ +package fortify + +//ProjectSearch is input for search project in Fortify +type ProjectSearch struct { + //set to 0 to retrieve everything back. default capped at 200. + ResultLimit int + ProjectName string +} + +//ProjectVersionCreateRequest is the request for project version creation API +type ProjectVersionCreateRequest struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Active bool `json:"active,omitempty"` + Committed bool `json:"committed,omitempty"` + Project ProjectRequest `json:"project,omitempty"` + IssueTemplateID string `json:"issueTemplateId,omitempty"` +} + +//ProjectRequest is the part of the request of project version creation +type ProjectRequest struct { + Name string `json:"name"` + Description string `json:"description"` + IssueTemplateID string `json:"issueTemplateId"` +} + +//BulkUpdateRequest contains a list of update requests +type BulkUpdateRequest struct { + Requests []UpdateRequest `json:"requests"` +} + +//UpdateRequest is one update request +type UpdateRequest struct { + URI string `json:"uri"` + HTTPVerb string `json:"httpVerb"` + PostData interface{} `json:"postData"` +} + +//PostDataRequest is part of the UpdateRequest +type PostDataRequest struct { + AttributeDefinitionID int `json:"attributeDefinitionId,omitempty"` + Values []ValueRequest `json:"values,omitempty"` + ResponsibilityGUID string `json:"responsibilityGuid,omitempty"` + Committed bool `json:"committed,omitempty"` + DisplayName string `json:"displayName,omitempty"` + Identifier string `json:"identifier,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Displayable bool `json:"displayable,omitempty"` +} + +//ValueRequest is part of PostDataRequest +type ValueRequest struct { + GUID string `json:"guid,omitempty"` +} diff --git a/clients/fortify/responses.go b/clients/fortify/responses.go new file mode 100644 index 0000000000000000000000000000000000000000..6576901b69393f1c4be23c853b99d09003fb6df6 --- /dev/null +++ b/clients/fortify/responses.go @@ -0,0 +1,82 @@ +package fortify + +//ProjectVersionsResponse is the response get a list of project versions +type ProjectVersionsResponse struct { + Data []ProjectVersionResponse `json:"data"` +} + +//ProjectVersionCreateResponse is the response for project version creation API +type ProjectVersionCreateResponse struct { + Data ProjectVersionResponse `json:"data"` +} + +//ProjectVersionResponse is the part of ProjectVersionCreateResponse +//{ +// "id": 834, +// "project": { +// "id": 832, +// "name": "57wg-spark-cell-fone-ui", +// "description": "https://code.il2.dso.mil/platform-one/products/57wg-spark-cell/fone/ui", +// "creationDate": "2021-06-03T23:21:57.000+0000", +// "createdBy": "tylergreene", +// "issueTemplateId": "Prioritized-HighRisk-Project-Template" +// }, +// "name": "0.1", +// "description": "", +// "createdBy": "tylergreene", +// "creationDate": "2021-06-03T23:21:57.000+0000", +// "sourceBasePath": null, +// "committed": true, +// "issueTemplateId": "Prioritized-HighRisk-Project-Template", +// "issueTemplateName": "Prioritized High Risk Issue Template", +// "loadProperties": null, +// "staleIssueTemplate": false, +// "snapshotOutOfDate": false, +// "refreshRequired": false, +// "attachmentsOutOfDate": false, +// "migrationVersion": null, +// "masterAttrGuid": "87f2364f-dcd4-49e6-861d-f8d3f351686b", +// "tracesOutOfDate": false, +// "issueTemplateModifiedTime": 1622762516688, +// "active": true, +// "obfuscatedId": null, +// "owner": "", +// "serverVersion": 20.1, +// "siteId": null, +// "latestScanId": null, +// "mode": "BASIC", +// "currentState": { +// "id": 834, +// "committed": true, +// "attentionRequired": false, +// "analysisResultsExist": true, +// "auditEnabled": true, +// "lastFprUploadDate": "2021-06-09T19:47:39.000+0000", +// "extraMessage": null, +// "analysisUploadEnabled": true, +// "batchBugSubmissionExists": false, +// "hasCustomIssues": false, +// "metricEvaluationDate": "2021-06-09T19:47:40.000+0000", +// "deltaPeriod": 7, +// "issueCountDelta": 42, +// "percentAuditedDelta": 0.0, +// "criticalPriorityIssueCountDelta": 0, +// "percentCriticalPriorityIssuesAuditedDelta": 0.0 +// }, +// "bugTrackerPluginId": null, +// "bugTrackerEnabled": false, +// "securityGroup": null, +// "status": null, +// "assignedIssuesCount": 0, +// "customTagValuesAutoApply": null, +// "autoPredict": null, +// "predictionPolicy": null, +// "_href": "https://fortify.il2.dso.mil/api/v1/projectVersions/834" +// }, +type ProjectVersionResponse struct { + ID int `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + CreatedBy string `json:"createdBy"` + CreationDate string `json:"creationDate"` +} diff --git a/clients/http_utils.go b/clients/http_utils.go index e22ca4cb0605ed4887da768aa8666fd1edfcf29e..2c1d5b7dd59fd73789fba6b9e8a4e8c15ab4013a 100644 --- a/clients/http_utils.go +++ b/clients/http_utils.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/tls" "encoding/json" + "errors" "io" "io/ioutil" "log" @@ -20,7 +21,7 @@ func CreatHTTPClient() *http.Client { } // callAPI - call API using the given client. Supported methods are: "GET", "POST" -func callAPI(client *http.Client, method string, endpoint string, authHeaderValue string, requestData interface{}, +func callAPI(client *http.Client, method string, endpoint string, authHeaderValue string, queryParams map[string]string, requestData interface{}, successStatusCode int) (responseBody []byte, err error) { // make the request var requestBody []byte @@ -49,16 +50,24 @@ func callAPI(client *http.Client, method string, endpoint string, authHeaderValu req.Header.Set("Authorization", authHeaderValue) } + if queryParams != nil { + q := req.URL.Query() + for k, v := range queryParams { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + // Send req using http client var resp *http.Response resp, err = client.Do(req) if err != nil { return nil, err } - //if resp.StatusCode != successStatusCode { - // err = errors.New("unexpected http status code: " + resp.Status) - // return - //} + if resp.StatusCode != successStatusCode { + err = errors.New("unexpected http status code: " + resp.Status) + return + } defer resp.Body.Close() // read the body @@ -73,17 +82,22 @@ func callAPI(client *http.Client, method string, endpoint string, authHeaderValu // Post - make an http post request func Post(client *http.Client, endpoint string, authHeaderValue string, requestData interface{}, successStatusCode int) (responseBody []byte, err error) { - return callAPI(client, "POST", endpoint, authHeaderValue, requestData, successStatusCode) + return callAPI(client, "POST", endpoint, authHeaderValue, nil, requestData, successStatusCode) } // Get - make an http get request -func Get(client *http.Client, endpoint string, authHeaderValue string, requestData interface{}, +func Get(client *http.Client, endpoint string, authHeaderValue string, queryParams map[string]string, requestData interface{}, successStatusCode int) (responseBody []byte, err error) { - return callAPI(client, "GET", endpoint, authHeaderValue, requestData, successStatusCode) + return callAPI(client, "GET", endpoint, authHeaderValue, queryParams, requestData, successStatusCode) } // Put - make an http put request func Put(client *http.Client, endpoint string, authHeaderValue string, requestData interface{}, successStatusCode int) (responseBody []byte, err error) { - return callAPI(client, "PUT", endpoint, authHeaderValue, requestData, successStatusCode) + return callAPI(client, "PUT", endpoint, authHeaderValue, nil, requestData, successStatusCode) +} + +// Delete - make an http delete request +func Delete(client *http.Client, endpoint string, authHeaderValue string, successStatusCode int) (responseBody []byte, err error) { + return callAPI(client, "DELETE", endpoint, authHeaderValue, nil, nil, successStatusCode) } diff --git a/clients/http_utils_test.go b/clients/http_utils_test.go index eca73a5415211bdb15d038fa65446099227b57fb..e541f2f69ef41a537f02843678a15a3cba73e7cb 100644 --- a/clients/http_utils_test.go +++ b/clients/http_utils_test.go @@ -54,6 +54,7 @@ func Test_callAPI(t *testing.T) { endpoint string authHeaderValue string requestData interface{} + queryParams map[string]string successStatusCode int } @@ -68,13 +69,13 @@ func Test_callAPI(t *testing.T) { // TODO: Add test cases. { name: "test callAPI success", - args: args{testHTTPClient, "GET", "http://test/endpoint", "header", testTypeObj, 200}, + args: args{testHTTPClient, "GET", "http://test/endpoint", "header", testTypeObj, nil, 200}, wantResponseBody: []byte{123, 125}, wantErr: false, }, { name: "test callAPI failure", - args: args{testHTTPClient, "GET", "http://test/endpoint", "header", testTypeObj, 200}, + args: args{testHTTPClient, "GET", "http://test/endpoint", "header", testTypeObj, nil, 200}, wantResponseBody: []byte{123, 125}, wantErr: true, }, @@ -98,7 +99,7 @@ func Test_callAPI(t *testing.T) { } t.Run(tt.name, func(t *testing.T) { - gotResponseBody, err := callAPI(tt.args.client, tt.args.method, tt.args.endpoint, tt.args.authHeaderValue, tt.args.requestData, tt.args.successStatusCode) + gotResponseBody, err := callAPI(tt.args.client, tt.args.method, tt.args.endpoint, tt.args.authHeaderValue, tt.args.queryParams, tt.args.requestData, tt.args.successStatusCode) if (err != nil) && !tt.wantErr { t.Errorf("callAPI() error = %v, wantErr %v", err, tt.wantErr) return @@ -122,6 +123,7 @@ func TestGetPostPut(t *testing.T) { endpoint string authHeaderValue string requestData interface{} + queryParams map[string]string successStatusCode int } @@ -135,7 +137,7 @@ func TestGetPostPut(t *testing.T) { }{ { name: "test callAPI success", - args: args{testHTTPClient, "http://test/endpoint", "header", testTypeObj, 200}, + args: args{testHTTPClient, "http://test/endpoint", "header", testTypeObj, nil, 200}, wantResponseBody: []byte{123, 125}, wantErr: false, }, @@ -155,7 +157,7 @@ func TestGetPostPut(t *testing.T) { httpmock.NewJsonResponderOrPanic(200, &testResponseOne{name: "test"}), ) t.Run(tt.name, func(t *testing.T) { - gotResponseBody, err := Get(tt.args.client, tt.args.endpoint, tt.args.authHeaderValue, tt.args.requestData, tt.args.successStatusCode) + gotResponseBody, err := Get(tt.args.client, tt.args.endpoint, tt.args.authHeaderValue, tt.args.queryParams, tt.args.requestData, tt.args.successStatusCode) if (err != nil) != tt.wantErr { t.Errorf("Post() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/clients/twistlock/client.go b/clients/twistlock/client.go index e1a5d37d7a12c9c022e31738c15bf9b79419f284..3ddde375c0191714af3ca3a222c1eb304cecd619 100644 --- a/clients/twistlock/client.go +++ b/clients/twistlock/client.go @@ -26,7 +26,7 @@ type Client struct { // NewClient - create new twistlock client func NewClient(serverURL string, username string, password string) Client { - client := clients.CreatHTTPClient() + client := &http.Client{} return Client{ serverAPIURL: serverURL + apiURLPrefix, username: username, @@ -68,7 +68,7 @@ func (r Client) GetRegistrySpecs() (*RegistrySpecsResponse, error) { endpoint := r.getEndpoint("registry") - body, err := clients.Get(r.httpClient, endpoint, authHeaderValue, nil, 200) + body, err := clients.Get(r.httpClient, endpoint, authHeaderValue, nil, nil, 200) if err != nil { return nil, err } diff --git a/config/crd/bases/customer.valkyrie.dso.mil_customers.yaml b/config/crd/bases/customer.valkyrie.dso.mil_customers.yaml index 943d3ba13b5f70e6b5309d74714e46b6cd99ee14..270ceee827bd51139873033403ea58e87c6b4530 100644 --- a/config/crd/bases/customer.valkyrie.dso.mil_customers.yaml +++ b/config/crd/bases/customer.valkyrie.dso.mil_customers.yaml @@ -39,13 +39,20 @@ spec: Group: description: Group is the Gitlab Group that belongs to this Customer properties: + description: + description: Description is the Gitlab Description for the group + type: string gitlabCredentialsName: 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 + description: Name is the name of the Group and will be used as + part of the URL in Gitlab + type: string + owner: + description: Owner is the Gitlab Username for the group owner type: string projects:omitempty: description: ProjectSpecs are for the GitLab projects managed diff --git a/config/crd/bases/fortify.valkyrie.dso.mil_fortifycredentials.yaml b/config/crd/bases/fortify.valkyrie.dso.mil_fortifycredentials.yaml new file mode 100644 index 0000000000000000000000000000000000000000..43fb1cc319b057c90a5e563615ee3de14b2299de --- /dev/null +++ b/config/crd/bases/fortify.valkyrie.dso.mil_fortifycredentials.yaml @@ -0,0 +1,78 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: fortifycredentials.fortify.valkyrie.dso.mil +spec: + group: fortify.valkyrie.dso.mil + names: + kind: FortifyCredential + listKind: FortifyCredentialList + plural: fortifycredentials + singular: fortifycredential + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: FortifyCredential is the Schema for the fortifycredentials 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: FortifyCredentialSpec defines the desired state of FortifyCredential + properties: + accessTokenSecretRef: + description: 'AccessTokenSecRef is the Secret Key Ref to the secret + containing the Fortify Access Token for the user. TODO: CI Access + Token is only valid for 1 year plus it doesn''t support delete operation. + We might need to get an admin access token. Need to check how long + admin token is valid for. might have to use username + pass' + properties: + key: + description: The key of the secret to select from. Must be a + valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + required: + - key + type: object + serverUrl: + description: 'ServerURL is the url for the Fortify server. For example: + https://fortify.il2.dso.mil' + type: string + type: object + status: + description: FortifyCredentialStatus defines the observed state of FortifyCredential + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/bases/gitlab.valkyrie.dso.mil_fortifypipelineconfigurations.yaml b/config/crd/bases/fortify.valkyrie.dso.mil_fortifypipelineconfigurations.yaml similarity index 57% rename from config/crd/bases/gitlab.valkyrie.dso.mil_fortifypipelineconfigurations.yaml rename to config/crd/bases/fortify.valkyrie.dso.mil_fortifypipelineconfigurations.yaml index 670c66b7763cf039624353aed65c6217263edb03..cca1db685e0eafd37a24ac3119b018d06d8ff207 100644 --- a/config/crd/bases/gitlab.valkyrie.dso.mil_fortifypipelineconfigurations.yaml +++ b/config/crd/bases/fortify.valkyrie.dso.mil_fortifypipelineconfigurations.yaml @@ -6,9 +6,9 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.4.1 creationTimestamp: null - name: fortifypipelineconfigurations.gitlab.valkyrie.dso.mil + name: fortifypipelineconfigurations.fortify.valkyrie.dso.mil spec: - group: gitlab.valkyrie.dso.mil + group: fortify.valkyrie.dso.mil names: kind: FortifyPipelineConfiguration listKind: FortifyPipelineConfigurationList @@ -38,18 +38,43 @@ spec: description: FortifyPipelineConfigurationSpec defines the desired state of FortifyPipelineConfiguration properties: - dummy: - description: test Dummy is an example field of Project. Edit project_types.go - to remove/update + credentialName: + description: FortifyCredentialName is the name of the Kind FortifyCredential + object in this namespace that contains authentication information. type: string + language: + description: 'Language is the language that the project uses, for + example: java, javascript etc' + type: string + projectName: + description: 'ProjectName is name of the fortify project, for example: + platform-one-devops-hello-pipeline-react-world' + type: string + required: + - credentialName + - language + - projectName type: object status: description: FortifyPipelineConfigurationStatus defines the observed state of FortifyPipelineConfiguration properties: - dummy: - description: test Dummy is an example field of Project. Edit project_types.go - to remove/update + createdTime: + description: CreatedTime is the timestamp when the project was created + by the controller. If the project already exists beforehand, this + will not be set. + format: date-time + type: string + lastUpdatedTime: + description: LastUpdatedTime is the latest timestamp when the project + was updated by the controller. + format: date-time + type: string + projectID: + description: ProjectID is the ID of the project in Fortify + type: integer + state: + description: State is the current state type: string type: object type: object diff --git a/config/crd/bases/gitlab.valkyrie.dso.mil_gitlabcredentials.yaml b/config/crd/bases/gitlab.valkyrie.dso.mil_gitlabcredentials.yaml index 1c6de61057e7caaa3a7a6d1af1fbead873b4f234..5cb102344f4501b150f2a586194d409afe17e97b 100644 --- a/config/crd/bases/gitlab.valkyrie.dso.mil_gitlabcredentials.yaml +++ b/config/crd/bases/gitlab.valkyrie.dso.mil_gitlabcredentials.yaml @@ -38,7 +38,7 @@ spec: this stores a Gitlab username and Access Token for communicating with the Gitlab API. properties: - access_token: + accessToken: description: AccessToken is the SecretRef to the secret containing the Gitlab Access Token for the user. properties: @@ -51,6 +51,10 @@ spec: name must be unique. type: string type: object + accessTokenKey: + description: AccessTokenKey is the key of the secret data that contains + the Gitlab Access Token for the user. + type: string url: description: URL is the url for the GitLab API that will be contacted. type: string @@ -58,17 +62,19 @@ spec: description: Username is the Gitlab username for the account that will be communicating with the Gitlab API type: string + required: + - accessTokenKey type: object status: description: GitlabCredentialsStatus defines the observed state of GitlabCredentials properties: - last_used_date: + lastUsedDate: 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 + - lastUsedDate type: object type: object served: true diff --git a/config/crd/bases/gitlab.valkyrie.dso.mil_groups.yaml b/config/crd/bases/gitlab.valkyrie.dso.mil_groups.yaml index ccbce4545053552211a798c4498b9aec3e83a25a..4138a6bb5329c89c2965e586bb339a2dd6b00128 100644 --- a/config/crd/bases/gitlab.valkyrie.dso.mil_groups.yaml +++ b/config/crd/bases/gitlab.valkyrie.dso.mil_groups.yaml @@ -36,13 +36,20 @@ spec: spec: description: GroupSpec defines the desired state of Group properties: + description: + description: Description is the Gitlab Description for the group + type: string gitlabCredentialsName: 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 + description: Name is the name of the Group and will be used as part + of the URL in Gitlab + type: string + owner: + description: Owner is the Gitlab Username for the group owner type: string projects:omitempty: description: ProjectSpecs are for the GitLab projects managed by this @@ -105,21 +112,21 @@ spec: status: description: GroupStatus defines the observed state of Group properties: - created_time: + createdTime: format: date-time type: string - gitlab_id: + gitlabId: format: int64 type: integer - last_updated_time: + lastUpdatedTime: format: date-time type: string state: type: string required: - - created_time - - gitlab_id - - last_updated_time + - createdTime + - gitlabId + - lastUpdatedTime - state type: object type: object diff --git a/config/crd/bases/gitlab.valkyrie.dso.mil_projects.yaml b/config/crd/bases/gitlab.valkyrie.dso.mil_projects.yaml index 873a40aca3154ec7f654afff14afe714fc2e58cd..8f9eced49d0679235335a673cb67727873c1aa16 100644 --- a/config/crd/bases/gitlab.valkyrie.dso.mil_projects.yaml +++ b/config/crd/bases/gitlab.valkyrie.dso.mil_projects.yaml @@ -90,7 +90,7 @@ spec: to remove/update type: string url: - description: Url is the url for the project in GitLab + description: URL is the url for the project in GitLab type: string required: - url diff --git a/config/crd/bases/twistlock.valkyrie.dso.mil_twistlockcredentials.yaml b/config/crd/bases/twistlock.valkyrie.dso.mil_twistlockcredentials.yaml new file mode 100644 index 0000000000000000000000000000000000000000..116c2c85151d38a0501b1786dfef889939691842 --- /dev/null +++ b/config/crd/bases/twistlock.valkyrie.dso.mil_twistlockcredentials.yaml @@ -0,0 +1,98 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: twistlockcredentials.twistlock.valkyrie.dso.mil +spec: + group: twistlock.valkyrie.dso.mil + names: + kind: TwistlockCredential + listKind: TwistlockCredentialList + plural: twistlockcredentials + singular: twistlockcredential + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: TwistlockCredential is the Schema for the twistlockcredential + 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: TwistlockCredentialSpec defines the desired state of TwistlockCredential + properties: + passwordSecretRef: + description: PasswordSecRef is the password secret used for API calls + to Twistlock + properties: + key: + description: The key of the secret to select from. Must be a + valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + required: + - key + type: object + serverUrl: + description: 'ServerURL is the url for the Fortify server. For example: + https://fortify.il2.dso.mil' + type: string + usernameSecretRef: + description: UsernameSecRef is the username secret used for API calls + to Twistlock + properties: + key: + description: The key of the secret to select from. Must be a + valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + required: + - key + type: object + required: + - passwordSecretRef + - serverUrl + - usernameSecretRef + type: object + status: + description: TwistlockCredentialStatus defines the observed state of TwistlockCredential + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/bases/gitlab.valkyrie.dso.mil_twistlockpipelineconfigurations.yaml b/config/crd/bases/twistlock.valkyrie.dso.mil_twistlockpipelineconfigurations.yaml similarity index 61% rename from config/crd/bases/gitlab.valkyrie.dso.mil_twistlockpipelineconfigurations.yaml rename to config/crd/bases/twistlock.valkyrie.dso.mil_twistlockpipelineconfigurations.yaml index 92b4e843508a827db82e24246af8eaf161c95b2c..0376467237f16ad76d5e9aa50dab5b49dfef278a 100644 --- a/config/crd/bases/gitlab.valkyrie.dso.mil_twistlockpipelineconfigurations.yaml +++ b/config/crd/bases/twistlock.valkyrie.dso.mil_twistlockpipelineconfigurations.yaml @@ -6,9 +6,9 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.4.1 creationTimestamp: null - name: twistlockpipelineconfigurations.gitlab.valkyrie.dso.mil + name: twistlockpipelineconfigurations.twistlock.valkyrie.dso.mil spec: - group: gitlab.valkyrie.dso.mil + group: twistlock.valkyrie.dso.mil names: kind: TwistlockPipelineConfiguration listKind: TwistlockPipelineConfigurationList @@ -38,22 +38,45 @@ spec: description: TwistlockPipelineConfigurationSpec defines the desired state of TwistlockPipelineConfiguration properties: - credentialId: + credentialName: + description: CredentialName is the name of the Kind TwistlockCredential + object in this namespace that contains authentication information. + type: string + registryCredentialId: description: RegistryCredentialID is the credential ID in Twistlock - used to access the registry. It's expected that the credential already - exists in Twistlock. + used to access RegistryHostname. It's expected that the credential + already exists in Twistlock. + type: string + registryHostname: + description: RegistryHostname is the registry used when creating project + in Twistlock type: string repository: description: 'Repository path for the gitlab repo, for example: platform-one/devops/hello-pipeline/react-world' type: string + required: + - credentialName + - registryCredentialId + - registryHostname + - repository type: object status: description: TwistlockPipelineConfigurationStatus defines the observed state of TwistlockPipelineConfiguration properties: - dummy: - description: test Dummy is an example field of Project. Edit project_types.go - to remove/update + createdTime: + description: CreatedTime is the timestamp when the project was created + by the controller. If the project already exists beforehand, this + will not be set. + format: date-time + type: string + lastUpdatedTime: + description: LastUpdatedTime is the latest timestamp when the project + was updated by the controller. + format: date-time + type: string + state: + description: State is the current state type: string type: object type: object diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index b7fc193f8c7c2342010b51bc816e5a3c6cd20341..65f299b21d55fb07e9f0727a95ad711b613b86a9 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -7,15 +7,17 @@ resources: - bases/gitlab.valkyrie.dso.mil_groups.yaml - bases/gitlab.valkyrie.dso.mil_projects.yaml - bases/gitlab.valkyrie.dso.mil_pipelines.yaml -- bases/gitlab.valkyrie.dso.mil_fortifypipelineconfigurations.yaml -- bases/gitlab.valkyrie.dso.mil_twistlockpipelineconfigurations.yaml - bases/gitlab.valkyrie.dso.mil_sonarqubepipelineconfigurations.yaml - bases/gitlab.valkyrie.dso.mil_sdelementspipelineconfigurations.yaml - bases/customer.valkyrie.dso.mil_authorizingofficials.yaml - bases/customer.valkyrie.dso.mil_systemowners.yaml - bases/customer.valkyrie.dso.mil_chiefinformationsecurityofficers.yaml - bases/sonarqube.valkyrie.dso.mil_projects.yaml +- bases/fortify.valkyrie.dso.mil_fortifycredentials.yaml +- bases/fortify.valkyrie.dso.mil_fortifypipelineconfigurations.yaml - bases/gitlab.valkyrie.dso.mil_gitlabcredentials.yaml +- bases/twistlock.valkyrie.dso.mil_twistlockpipelineconfigurations.yaml +- bases/twistlock.valkyrie.dso.mil_twistlockcredentials.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -33,7 +35,9 @@ patchesStrategicMerge: #- patches/webhook_in_authorizingofficials.yaml #- patches/webhook_in_systemowners.yaml #- patches/webhook_in_chiefinformationsecurityofficers.yaml +#- patches/webhook_in_fortifycredentials.yaml #- patches/webhook_in_gitlabcredentials.yaml +#- patches/webhook_in_twistlockcredentials.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. @@ -50,7 +54,9 @@ patchesStrategicMerge: #- patches/cainjection_in_authorizingofficials.yaml #- patches/cainjection_in_systemowners.yaml #- patches/cainjection_in_chiefinformationsecurityofficers.yaml +#- patches/cainjection_in_fortifycredentials.yaml #- patches/cainjection_in_gitlabcredentials.yaml +#- patches/cainjection_in_twistlockcredentials.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_fortifycredentials.yaml b/config/crd/patches/cainjection_in_fortifycredentials.yaml new file mode 100644 index 0000000000000000000000000000000000000000..755edd84720208992733b95989b0b0ac5254640c --- /dev/null +++ b/config/crd/patches/cainjection_in_fortifycredentials.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: fortifycredentials.fortify.valkyrie.dso.mil diff --git a/config/crd/patches/cainjection_in_fortifypipelineconfigurations.yaml b/config/crd/patches/cainjection_in_fortifypipelineconfigurations.yaml index f3ba6e3d38781321a33cf8cd03b76bcfba9c8b63..c08ab079f7c591ba478c16b2bebcb2911671cc57 100644 --- a/config/crd/patches/cainjection_in_fortifypipelineconfigurations.yaml +++ b/config/crd/patches/cainjection_in_fortifypipelineconfigurations.yaml @@ -4,4 +4,4 @@ kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) - name: fortifypipelineconfigurations.gitlab.valkyrie.dso.mil + name: fortifypipelineconfigurations.fortify.valkyrie.dso.mil diff --git a/config/crd/patches/cainjection_in_twistlockcredentials.yaml b/config/crd/patches/cainjection_in_twistlockcredentials.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3718df21032208e14882620fb5b94bb503b7b9b2 --- /dev/null +++ b/config/crd/patches/cainjection_in_twistlockcredentials.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: twistlockcredentials.twistlock.valkyrie.dso.mil diff --git a/config/crd/patches/cainjection_in_twistlockpipelineconfigurations.yaml b/config/crd/patches/cainjection_in_twistlockpipelineconfigurations.yaml index 965e86e0ae3b5e91c6b510638ae5383143da1cf7..7b1ebe9e16ab7f5a36430d8dddb360a5efbaffe5 100644 --- a/config/crd/patches/cainjection_in_twistlockpipelineconfigurations.yaml +++ b/config/crd/patches/cainjection_in_twistlockpipelineconfigurations.yaml @@ -4,4 +4,4 @@ kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) - name: twistlockpipelineconfigurations.gitlab.valkyrie.dso.mil + name: twistlockpipelineconfigurations.twistlock.valkyrie.dso.mil diff --git a/config/crd/patches/webhook_in_fortifycredentials.yaml b/config/crd/patches/webhook_in_fortifycredentials.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a7b849235e91731a53a6cad13a16ab03c802e5b6 --- /dev/null +++ b/config/crd/patches/webhook_in_fortifycredentials.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: fortifycredentials.fortify.valkyrie.dso.mil +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_fortifypipelineconfigurations.yaml b/config/crd/patches/webhook_in_fortifypipelineconfigurations.yaml index 242c5811f5ebcd6767976e3f550a437b823438a6..a0fcf233714dfc07490c792657a887ac96ee286e 100644 --- a/config/crd/patches/webhook_in_fortifypipelineconfigurations.yaml +++ b/config/crd/patches/webhook_in_fortifypipelineconfigurations.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: fortifypipelineconfigurations.gitlab.valkyrie.dso.mil + name: fortifypipelineconfigurations.fortify.valkyrie.dso.mil spec: conversion: strategy: Webhook @@ -12,3 +12,5 @@ spec: namespace: system name: webhook-service path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_twistlockcredentials.yaml b/config/crd/patches/webhook_in_twistlockcredentials.yaml new file mode 100644 index 0000000000000000000000000000000000000000..472b2fcb5daaffa3b65a1b341bcf5ef2c8eeb8f4 --- /dev/null +++ b/config/crd/patches/webhook_in_twistlockcredentials.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: twistlockcredentials.twistlock.valkyrie.dso.mil +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_twistlockpipelineconfigurations.yaml b/config/crd/patches/webhook_in_twistlockpipelineconfigurations.yaml index 3011b6e02b4f2faa7b54721797443579837a2588..584e4ad27d979c0e405cc84718105b787698af2b 100644 --- a/config/crd/patches/webhook_in_twistlockpipelineconfigurations.yaml +++ b/config/crd/patches/webhook_in_twistlockpipelineconfigurations.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: twistlockpipelineconfigurations.gitlab.valkyrie.dso.mil + name: twistlockpipelineconfigurations.twistlock.valkyrie.dso.mil spec: conversion: strategy: Webhook diff --git a/config/rbac/fortifycredential_editor_role.yaml b/config/rbac/fortifycredential_editor_role.yaml new file mode 100644 index 0000000000000000000000000000000000000000..85e3e4bc2da6309acfdc827b499b60f29fc1beb1 --- /dev/null +++ b/config/rbac/fortifycredential_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit fortifycredentials. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: fortifycredential-editor-role +rules: +- apiGroups: + - fortify.valkyrie.dso.mil + resources: + - fortifycredentials + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - fortify.valkyrie.dso.mil + resources: + - fortifycredentials/status + verbs: + - get diff --git a/config/rbac/fortifycredential_viewer_role.yaml b/config/rbac/fortifycredential_viewer_role.yaml new file mode 100644 index 0000000000000000000000000000000000000000..67c184ddac986b74574f92c6032d55302a9f19e9 --- /dev/null +++ b/config/rbac/fortifycredential_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view fortifycredentials. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: fortifycredential-viewer-role +rules: +- apiGroups: + - fortify.valkyrie.dso.mil + resources: + - fortifycredentials + verbs: + - get + - list + - watch +- apiGroups: + - fortify.valkyrie.dso.mil + resources: + - fortifycredentials/status + verbs: + - get diff --git a/config/rbac/fortifypipelineconfiguration_editor_role.yaml b/config/rbac/fortifypipelineconfiguration_editor_role.yaml index 3875b3216b1d2a1b7a890dc847de560b5c3bd410..56e90d921a9f434b4e023fbbde80e0f3c358b767 100644 --- a/config/rbac/fortifypipelineconfiguration_editor_role.yaml +++ b/config/rbac/fortifypipelineconfiguration_editor_role.yaml @@ -5,7 +5,7 @@ metadata: name: fortifypipelineconfiguration-editor-role rules: - apiGroups: - - gitlab.valkyrie.dso.mil + - fortify.valkyrie.dso.mil resources: - fortifypipelineconfigurations verbs: @@ -17,7 +17,7 @@ rules: - update - watch - apiGroups: - - gitlab.valkyrie.dso.mil + - fortify.valkyrie.dso.mil resources: - fortifypipelineconfigurations/status verbs: diff --git a/config/rbac/fortifypipelineconfiguration_viewer_role.yaml b/config/rbac/fortifypipelineconfiguration_viewer_role.yaml index 3acb0905f1d63be396db1b147fa3f67f580044af..e4e61e8b2bc0fb82bd4ddaed8d56c27f6c1a0c9f 100644 --- a/config/rbac/fortifypipelineconfiguration_viewer_role.yaml +++ b/config/rbac/fortifypipelineconfiguration_viewer_role.yaml @@ -5,7 +5,7 @@ metadata: name: fortifypipelineconfiguration-viewer-role rules: - apiGroups: - - gitlab.valkyrie.dso.mil + - fortify.valkyrie.dso.mil resources: - fortifypipelineconfigurations verbs: @@ -13,7 +13,7 @@ rules: - list - watch - apiGroups: - - gitlab.valkyrie.dso.mil + - fortify.valkyrie.dso.mil resources: - fortifypipelineconfigurations/status verbs: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index f6254bfdb4e7fd470a339e6f0a6016b7d3879060..07ec3af14a516b890e7b022305f246ea3a52b52b 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -153,7 +153,33 @@ rules: - patch - update - apiGroups: - - gitlab.valkyrie.dso.mil + - fortify.valkyrie.dso.mil + resources: + - fortifycredentials + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - fortify.valkyrie.dso.mil + resources: + - fortifycredentials/finalizers + verbs: + - update +- apiGroups: + - fortify.valkyrie.dso.mil + resources: + - fortifycredentials/status + verbs: + - get + - patch + - update +- apiGroups: + - fortify.valkyrie.dso.mil resources: - fortifypipelineconfigurations verbs: @@ -165,13 +191,13 @@ rules: - update - watch - apiGroups: - - gitlab.valkyrie.dso.mil + - fortify.valkyrie.dso.mil resources: - fortifypipelineconfigurations/finalizers verbs: - update - apiGroups: - - gitlab.valkyrie.dso.mil + - fortify.valkyrie.dso.mil resources: - fortifypipelineconfigurations/status verbs: @@ -386,3 +412,29 @@ rules: - get - patch - update +- apiGroups: + - twistlock.valkyrie.dso.mil + resources: + - twistlockcredentials + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - twistlock.valkyrie.dso.mil + resources: + - twistlockcredentials/finalizers + verbs: + - update +- apiGroups: + - twistlock.valkyrie.dso.mil + resources: + - twistlockcredentials/status + verbs: + - get + - patch + - update diff --git a/config/rbac/twistlockpipelineconfiguration_editor_role.yaml b/config/rbac/twistlockpipelineconfiguration_editor_role.yaml index 8e6736a4512e213c5063ce260af48000b94192c8..c32400934f4dd7bec3c78b2de89bd90cef162c82 100644 --- a/config/rbac/twistlockpipelineconfiguration_editor_role.yaml +++ b/config/rbac/twistlockpipelineconfiguration_editor_role.yaml @@ -5,7 +5,7 @@ metadata: name: twistlockpipelineconfiguration-editor-role rules: - apiGroups: - - gitlab.valkyrie.dso.mil + - twistlock.valkyrie.dso.mil resources: - twistlockpipelineconfigurations verbs: @@ -17,7 +17,7 @@ rules: - update - watch - apiGroups: - - gitlab.valkyrie.dso.mil + - twistlock.valkyrie.dso.mil resources: - twistlockpipelineconfigurations/status verbs: diff --git a/config/rbac/twistlockpipelineconfiguration_viewer_role.yaml b/config/rbac/twistlockpipelineconfiguration_viewer_role.yaml index 56dda60234da47bd36df99dacd5c25824aecc162..6d98e6fce7139d49ad9cd01f005dcc507a1e5a88 100644 --- a/config/rbac/twistlockpipelineconfiguration_viewer_role.yaml +++ b/config/rbac/twistlockpipelineconfiguration_viewer_role.yaml @@ -5,7 +5,7 @@ metadata: name: twistlockpipelineconfiguration-viewer-role rules: - apiGroups: - - gitlab.valkyrie.dso.mil + - twistlock.valkyrie.dso.mil resources: - twistlockpipelineconfigurations verbs: @@ -13,7 +13,7 @@ rules: - list - watch - apiGroups: - - gitlab.valkyrie.dso.mil + - twistlock.valkyrie.dso.mil resources: - twistlockpipelineconfigurations/status verbs: diff --git a/config/rbac/twistlockproperties_editor_role.yaml b/config/rbac/twistlockproperties_editor_role.yaml new file mode 100644 index 0000000000000000000000000000000000000000..48b95c81fd0e8867b10c1dace478150949a07707 --- /dev/null +++ b/config/rbac/twistlockproperties_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit twistlockcredentials. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: twistlockcredential-editor-role +rules: +- apiGroups: + - twistlock.valkyrie.dso.mil + resources: + - twistlockcredentials + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - twistlock.valkyrie.dso.mil + resources: + - twistlockcredentials/status + verbs: + - get diff --git a/config/rbac/twistlockproperties_viewer_role.yaml b/config/rbac/twistlockproperties_viewer_role.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b145728ffe3371fef9f77c85b268eb2e76009c35 --- /dev/null +++ b/config/rbac/twistlockproperties_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view twistlockcredentials. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: twistlockcredential-viewer-role +rules: +- apiGroups: + - twistlock.valkyrie.dso.mil + resources: + - twistlockcredentials + verbs: + - get + - list + - watch +- apiGroups: + - twistlock.valkyrie.dso.mil + resources: + - twistlockcredentials/status + verbs: + - get diff --git a/config/samples/fortify_v1alpha1_fortifycredential.yaml b/config/samples/fortify_v1alpha1_fortifycredential.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d251e73828e21e11941c66175a88be18545fdde4 --- /dev/null +++ b/config/samples/fortify_v1alpha1_fortifycredential.yaml @@ -0,0 +1,10 @@ +apiVersion: fortify.valkyrie.dso.mil/v1alpha1 +kind: FortifyCredential +metadata: + name: fortifycredential-sample +spec: + serverUrl: "https://fortify.il2.dso.mil" + accessTokenSecretRef: + name: fortify-secret + key: accessToken + diff --git a/config/samples/fortify_v1alpha1_fortifypipelineconfiguration.yaml b/config/samples/fortify_v1alpha1_fortifypipelineconfiguration.yaml new file mode 100644 index 0000000000000000000000000000000000000000..eafd8938bd073dd35c8d13515a20c8e30c2d3503 --- /dev/null +++ b/config/samples/fortify_v1alpha1_fortifypipelineconfiguration.yaml @@ -0,0 +1,9 @@ +apiVersion: fortify.valkyrie.dso.mil/v1alpha1 +kind: FortifyPipelineConfiguration +metadata: + name: fortifypipelineconfiguration-sample +spec: + projectName: "Valkyrie10" + language: "java" + credentialName: fortifycredential-sample + diff --git a/config/samples/gitlab_v1alpha1_fortifypipelineconfiguration.yaml b/config/samples/gitlab_v1alpha1_fortifypipelineconfiguration.yaml deleted file mode 100644 index 2f031ff1cf07e81161c4516a7db17411d5d7de51..0000000000000000000000000000000000000000 --- a/config/samples/gitlab_v1alpha1_fortifypipelineconfiguration.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: gitlab.valkyrie.dso.mil/v1alpha1 -kind: FortifyPipelineConfiguration -metadata: - name: fortifypipelineconfiguration-sample -spec: - # Add fields here - foo: bar diff --git a/config/samples/gitlab_v1alpha1_twistlockpipelineconfiguration.yaml b/config/samples/gitlab_v1alpha1_twistlockpipelineconfiguration.yaml deleted file mode 100644 index e67a8a2b84531a7a01442d0884b2c070ebd16733..0000000000000000000000000000000000000000 --- a/config/samples/gitlab_v1alpha1_twistlockpipelineconfiguration.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: gitlab.valkyrie.dso.mil/v1alpha1 -kind: TwistlockPipelineConfiguration -metadata: - name: twistlockpipelineconfiguration-sample -spec: - repository: "02" -# credentialId: "test" - credentialId: "basic_auth_test" diff --git a/config/samples/twistlock_v1alpha1_twistlockcredential.yaml b/config/samples/twistlock_v1alpha1_twistlockcredential.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f8215a5d1d186a9e45fa8388d96b7d8be5de6dc5 --- /dev/null +++ b/config/samples/twistlock_v1alpha1_twistlockcredential.yaml @@ -0,0 +1,12 @@ +apiVersion: twistlock.valkyrie.dso.mil/v1alpha1 +kind: TwistlockCredential +metadata: + name: twistlockcredential-sample +spec: + serverUrl: "https://twistlock.bigbang.dev" + usernameSecretRef: + name: twistlock-secret + key: username + passwordSecretRef: + name: twistlock-secret + key: password diff --git a/config/samples/twistlock_v1alpha1_twistlockpipelineconfiguration.yaml b/config/samples/twistlock_v1alpha1_twistlockpipelineconfiguration.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0f0f9d4e1fb85b684f75ae9980b837f7681fbe8c --- /dev/null +++ b/config/samples/twistlock_v1alpha1_twistlockpipelineconfiguration.yaml @@ -0,0 +1,9 @@ +apiVersion: twistlock.valkyrie.dso.mil/v1alpha1 +kind: TwistlockPipelineConfiguration +metadata: + name: twistlockpipelineconfiguration-sample +spec: + repository: "02" + registryHostname: "registry.il2.dso.mil" + registryCredentialId: "basic_auth_test" + credentialName: twistlockcredential-sample \ No newline at end of file diff --git a/controllers/fortify/fortifycredential_controller.go b/controllers/fortify/fortifycredential_controller.go new file mode 100644 index 0000000000000000000000000000000000000000..873f19fa32bc41a867ee0a7611fe3275310c9cbc --- /dev/null +++ b/controllers/fortify/fortifycredential_controller.go @@ -0,0 +1,55 @@ +/* +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 fortify + +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" + + fortifyv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/fortify/v1alpha1" +) + +// CredentialReconciler reconciles a FortifyCredential object +type CredentialReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=fortify.valkyrie.dso.mil,resources=fortifycredentials,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=fortify.valkyrie.dso.mil,resources=fortifycredentials/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=fortify.valkyrie.dso.mil,resources=fortifycredentials/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. +func (r *CredentialReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = r.Log.WithValues("fortifycredential", req.NamespacedName) + + // your logic here + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *CredentialReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&fortifyv1alpha1.FortifyCredential{}). + Complete(r) +} diff --git a/controllers/fortify/fortifycredential_controller_test.go b/controllers/fortify/fortifycredential_controller_test.go new file mode 100755 index 0000000000000000000000000000000000000000..baafeba7daf2221282e6488b61890e7a115d35d0 --- /dev/null +++ b/controllers/fortify/fortifycredential_controller_test.go @@ -0,0 +1,65 @@ +package fortify + +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/fortify/v1alpha1" +) + +var _ = Describe("fortifycredential_controller", func() { + Describe("Reconcile", func() { + logger := MockLogger{ + WithValuesKeysAndValues: make([]interface{}, 0), + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + sut := CredentialReconciler{ + 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("fortifycredential")) + 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(&v1alpha1.FortifyCredential{}, &v1alpha1.FortifyCredentialList{}) + sut := CredentialReconciler{ + 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/fortify/fortifypipelineconfiguration_controller.go b/controllers/fortify/fortifypipelineconfiguration_controller.go new file mode 100644 index 0000000000000000000000000000000000000000..90609b36e0859b8a305e9af67817d9b60fd727b3 --- /dev/null +++ b/controllers/fortify/fortifypipelineconfiguration_controller.go @@ -0,0 +1,477 @@ +/* +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 fortify + +import ( + "context" + "github.com/go-logr/logr" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "strconv" + "valkyrie.dso.mil/valkyrie-api/clients/fortify" + utils "valkyrie.dso.mil/valkyrie-api/controllers" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + fortifyv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/fortify/v1alpha1" +) + +const finalizerName = "fortify.valkyrie.dso.mil/fortify_finalizer" + +const ( + projectVersionSubURL = "/projectVersions/" + statusUpdateError = "error updating resource status" +) + +// PipelineConfigurationReconciler reconciles a FortifyPipelineConfiguration object +type PipelineConfigurationReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=fortify.valkyrie.dso.mil,resources=fortifypipelineconfigurations,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=fortify.valkyrie.dso.mil,resources=fortifypipelineconfigurations/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=fortify.valkyrie.dso.mil,resources=fortifypipelineconfigurations/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. +func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) { + log := r.Log.WithValues("FortifyPipelineConfiguration", req.NamespacedName) + log.Info("in Fortify Controller") + + //default result + result = ctrl.Result{} + + var fortifyConfig fortifyv1alpha1.FortifyPipelineConfiguration + if err = r.Get(ctx, req.NamespacedName, &fortifyConfig); err != nil { + log.Error(err, "unable to fetch the custom resources") + err = client.IgnoreNotFound(err) + // 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 + } + + // Get the Fortify Credential + var fortifyCredentialName = types.NamespacedName{ + Namespace: req.Namespace, + Name: fortifyConfig.Spec.FortifyCredentialName, + } + + var fortifyCredential fortifyv1alpha1.FortifyCredential + if err = r.Get(ctx, fortifyCredentialName, &fortifyCredential); err != nil { + log.Error(err, "unable to fetch Fortify credential") + fortifyConfig.Status.State = "fortify credential not found." + if err2 := r.Status().Update(ctx, &fortifyConfig); err2 != nil { + log.Error(err2, statusUpdateError) + err = err2 + return + } + return + } + + //Get the token secret + var secretName = types.NamespacedName{ + Namespace: req.Namespace, + Name: fortifyCredential.Spec.AccessTokenSecRef.Name, + } + + var secret v1.Secret + if err = r.Get(ctx, secretName, &secret); err != nil { + log.Error(err, "unable to fetch secret from fortify credential") + fortifyConfig.Status.State = "secret from fortify credential not found." + if err2 := r.Status().Update(ctx, &fortifyConfig); err2 != nil { + log.Error(err2, statusUpdateError) + err = err2 + return + } + return + } + + //create Fortify client for API calls + accessToken := string(secret.Data[fortifyCredential.Spec.AccessTokenSecRef.Key]) + fc := fortify.NewClient(fortifyCredential.Spec.ServerURL, accessToken) + + // examine DeletionTimestamp to determine if object is under deletion + if fortifyConfig.ObjectMeta.DeletionTimestamp.IsZero() { + err = r.handleCreateOrUpdate(ctx, fortifyConfig, log, fc) + } else { + err = r.handleDelete(ctx, fortifyConfig, log, fc) + } + + return +} + +func (r *PipelineConfigurationReconciler) handleCreate(ctx context.Context, fortifyConfig fortifyv1alpha1.FortifyPipelineConfiguration, log logr.Logger, fc *fortify.Client) (err error) { + //else we create a new one + log.Info("creating a new Fortify project: " + fortifyConfig.Spec.ProjectName) + createRequest := fortify.ProjectVersionCreateRequest{ + Name: "0.1", + Description: "Created by Valkyrie", + Active: true, + Committed: false, + Project: fortify.ProjectRequest{ + Name: fortifyConfig.Spec.ProjectName, + Description: "Created by Valkyrie", + IssueTemplateID: "Prioritized-HighRisk-Project-Template", + }, + IssueTemplateID: "Prioritized-HighRisk-Project-Template", + } + + var createResponse fortify.ProjectVersionCreateResponse + createResponse, err = fc.CreateProjectVersion(createRequest) + if err != nil { + log.Error(err, "error creating fortify project: "+fortifyConfig.Spec.ProjectName) + return + } + + //update project settings + bulkUpdateRequest := createBulkUpdateRequest(fc.GetServerAPIBaseURL(), strconv.Itoa(createResponse.Data.ID), + fortifyConfig.Spec.Language) + err = fc.BulkUpdate(*bulkUpdateRequest) + if err != nil { + log.Error(err, "error updating fortify project setting after creation: "+fortifyConfig.Spec.ProjectName) + return + } + + //update status + fortifyConfig.Status.ProjectID = &createResponse.Data.ID + createdTime := metav1.Now() + fortifyConfig.Status.CreatedTime = &createdTime + fortifyConfig.Status.State = "project successfully created" + if err = r.Status().Update(ctx, &fortifyConfig); err != nil { + log.Error(err, statusUpdateError) + return + } + log.Info("successfully created a new Fortify project: " + fortifyConfig.Spec.ProjectName) + return +} + +func (r *PipelineConfigurationReconciler) handleUpdate(ctx context.Context, fortifyConfig fortifyv1alpha1.FortifyPipelineConfiguration, + log logr.Logger, fc *fortify.Client, existingVersionID int) (err error) { + + log.Info("updating Fortify project: " + fortifyConfig.Spec.ProjectName) + bulkUpdateRequest := createBulkUpdateRequest(fc.GetServerAPIBaseURL(), strconv.Itoa(existingVersionID), + fortifyConfig.Spec.Language) + err = fc.BulkUpdate(*bulkUpdateRequest) + if err != nil { + log.Error(err, "error updating fortify project: "+fortifyConfig.Spec.ProjectName) + return + } + + //update status + fortifyConfig.Status.ProjectID = &existingVersionID + updatedTime := metav1.Now() + fortifyConfig.Status.LastUpdatedTime = &updatedTime + fortifyConfig.Status.State = "project successfully updated" + if err = r.Status().Update(ctx, &fortifyConfig); err != nil { + log.Error(err, statusUpdateError) + return + } + log.Info("successfully updated Fortify project: " + fortifyConfig.Spec.ProjectName) + return +} + +func (r *PipelineConfigurationReconciler) handleCreateOrUpdate(ctx context.Context, fortifyConfig fortifyv1alpha1.FortifyPipelineConfiguration, log logr.Logger, fc *fortify.Client) (err error) { + // The object is not being deleted, so if it does not have our finalizer, + // then lets add the finalizer and update the object. This is equivalent + // registering our finalizer. + if !utils.ContainsString(fortifyConfig.GetFinalizers(), finalizerName) { + controllerutil.AddFinalizer(&fortifyConfig, finalizerName) + if err = r.Update(ctx, &fortifyConfig); err != nil { + log.Error(err, "error adding finalizer to list") + return + } + } + + //check if project already exists in Fortify + var searchResult fortify.ProjectVersionsResponse + searchResult, err = fc.SearchProjectVersions(&fortify.ProjectSearch{ + ResultLimit: 1, + ProjectName: fortifyConfig.Spec.ProjectName, + }) + if err != nil { + log.Error(err, "error search for existing projects in Fortify") + return + } + + existingVersionID := -1 + //gets the existing version ID + if len(searchResult.Data) != 0 { + existingVersionID = searchResult.Data[0].ID + log.Info("found existing project in Fortify, Version ID: " + strconv.Itoa(existingVersionID)) + } + + //if project already exists in Fortify, we update it + if existingVersionID != -1 { + err = r.handleUpdate(ctx, fortifyConfig, log, fc, existingVersionID) + } else { + err = r.handleCreate(ctx, fortifyConfig, log, fc) + } + return +} + +func (r *PipelineConfigurationReconciler) handleDelete(ctx context.Context, fortifyConfig fortifyv1alpha1.FortifyPipelineConfiguration, log logr.Logger, fc *fortify.Client) (err error) { + // The object is being deleted + if utils.ContainsString(fortifyConfig.GetFinalizers(), finalizerName) { + // our finalizer is present, let's remove the project from Fortify. + log.Info("removing project from Fortify: " + fortifyConfig.Spec.ProjectName) + existingProjectID := fortifyConfig.Status.ProjectID + if existingProjectID != nil { + err = fc.DeleteProjectVersion(*existingProjectID) + if err != nil { + log.Error(err, "error deleting project from Fortify") + return + } + } else { + log.Info("no existing project ID found. skip removing project from Fortify: " + fortifyConfig.Spec.ProjectName) + } + log.Info("successfully removed project from Fortify: " + fortifyConfig.Spec.ProjectName) + + // remove our finalizer from the list and update it. + controllerutil.RemoveFinalizer(&fortifyConfig, finalizerName) + if err = r.Update(ctx, &fortifyConfig); err != nil { + log.Error(err, "error removing finalizer from list") + return + } + } + return +} + +func createBulkUpdateRequest(serverAPIURL string, versionID string, language string) *fortify.BulkUpdateRequest { + return &fortify.BulkUpdateRequest{Requests: []fortify.UpdateRequest{ + { + URI: serverAPIURL + projectVersionSubURL + versionID + "/attributes", + HTTPVerb: "PUT", + PostData: []fortify.PostDataRequest{ + { + AttributeDefinitionID: 1, + Values: []fortify.ValueRequest{ + { + GUID: "High", + }, + }, + }, + { + AttributeDefinitionID: 5, + Values: []fortify.ValueRequest{ + { + GUID: "Active", + }, + }, + }, + { + AttributeDefinitionID: 6, + Values: []fortify.ValueRequest{ + { + GUID: "Internal", + }, + }, + }, + { + AttributeDefinitionID: 7, + Values: []fortify.ValueRequest{ + { + GUID: "internalnetwork", + }, + }, + }, + { + AttributeDefinitionID: 8, + Values: []fortify.ValueRequest{ + { + GUID: "App", + }, + }, + }, + { + AttributeDefinitionID: 9, + Values: []fortify.ValueRequest{ + { + GUID: "NA", + }, + }, + }, + { + AttributeDefinitionID: 10, + Values: []fortify.ValueRequest{ + { + GUID: "WA", + }, + }, + }, + { + AttributeDefinitionID: 11, + Values: []fortify.ValueRequest{ + { + GUID: language, + }, + }, + }, + { + AttributeDefinitionID: 12, + Values: []fortify.ValueRequest{ + { + GUID: "None", + }, + }, + }, + }, + }, + { + URI: serverAPIURL + projectVersionSubURL + versionID + "/responsibilities", + HTTPVerb: "PUT", + PostData: []fortify.PostDataRequest{ + { + ResponsibilityGUID: "projectmanager", + }, + { + ResponsibilityGUID: "securitychampion", + }, + { + ResponsibilityGUID: "developmentmanager", + }, + { + ResponsibilityGUID: "superuser", + }, + }, + }, + { + URI: serverAPIURL + projectVersionSubURL + versionID + "?hideProgress=true", + HTTPVerb: "PUT", + PostData: fortify.ProjectVersionCreateRequest{ + Committed: true, + }, + }, + { + URI: serverAPIURL + projectVersionSubURL + versionID + "/resultProcessingRules", + HTTPVerb: "PUT", + PostData: []fortify.PostDataRequest{ + { + DisplayName: "Require approval if the Build Project is different between scans", + Identifier: "com.fortify.manager.BLL.processingrules.BuildProjectProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Check external metadata file versions in scan against versions on server.", + Identifier: "com.fortify.manager.BLL.processingrules.ExternalListVersionProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Require approval if file count differs by more than 10%", + Identifier: "com.fortify.manager.BLL.processingrules.FileCountProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Perform Force Instance ID migration on upload", + Identifier: "com.fortify.manager.BLL.processingrules.ForceMigrationProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Require approval if result has Fortify Java Annotations", + Identifier: "com.fortify.manager.BLL.processingrules.FortifyAnnotationsProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Require approval if line count differs by more than 10%", + Identifier: "com.fortify.manager.BLL.processingrules.LOCCountProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Automatically perform Instance ID migration on upload", + Identifier: "com.fortify.manager.BLL.processingrules.MigrationProcessingRule", + Enabled: true, + Displayable: true, + }, + { + DisplayName: "Require approval if the engine version of a scan is newer than the engine version of the previous scan", + Identifier: "com.fortify.manager.BLL.processingrules.NewerEngineVersionProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Ignore SCA scans performed in Quick Scan mode", + Identifier: "com.fortify.manager.BLL.processingrules.QuickScanProcessingRule", + Enabled: true, + Displayable: true, + }, + { + DisplayName: "Require approval if the rulepacks used in the scan do not match the rulepacks used in the previous scan", + Identifier: "com.fortify.manager.BLL.processingrules.RulePackVersionProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Require approval if SCA or WebInspect Agent scan does not have valid certification", + Identifier: "com.fortify.manager.BLL.processingrules.ValidCertificationProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Require approval if result has analysis warnings", + Identifier: "com.fortify.manager.BLL.processingrules.WarningProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Warn if audit information includes unknown custom tag", + Identifier: "com.fortify.manager.BLL.processingrules.UnknownOrDisallowedAuditedAttrChecker", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Require the issue audit permission to upload audited analysis files", + Identifier: "com.fortify.manager.BLL.processingrules.AuditedAnalysisRule", + Enabled: true, + Displayable: true, + }, + { + DisplayName: "Disallow upload of analysis results if there is one pending approval", + Identifier: "com.fortify.manager.BLL.processingrules.PendingApprovalChecker", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Disallow approval for processing if an earlier artifact requires approval", + Identifier: "com.fortify.manager.BLL.processingrules.VetoCascadingApprovalProcessingRule", + Enabled: false, + Displayable: true, + }, + }, + }, + }} +} + +// SetupWithManager sets up the controller with the Manager. +func (r *PipelineConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error { + pred := predicate.GenerationChangedPredicate{} + return ctrl.NewControllerManagedBy(mgr). + For(&fortifyv1alpha1.FortifyPipelineConfiguration{}). + WithEventFilter(pred). + Complete(r) +} diff --git a/controllers/fortify/fortifypipelineconfiguration_controller_test.go b/controllers/fortify/fortifypipelineconfiguration_controller_test.go new file mode 100755 index 0000000000000000000000000000000000000000..0adbbe6891368a56e88978b5693d5edda8214cbb --- /dev/null +++ b/controllers/fortify/fortifypipelineconfiguration_controller_test.go @@ -0,0 +1,1008 @@ +package fortify + +import ( + "context" + "errors" + "github.com/jarcoal/httpmock" + . "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" + "valkyrie.dso.mil/valkyrie-api/apis/fortify/v1alpha1" + "valkyrie.dso.mil/valkyrie-api/clients/fortify" +) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.FortifyPipelineConfiguration{}, &v1alpha1.FortifyPipelineConfigurationList{}) + 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 := PipelineConfigurationReconciler{ + 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 the custom resources")) + }) + 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 := v1alpha1.SchemeBuilder.Register(&v1alpha1.FortifyPipelineConfiguration{}, &v1alpha1.FortifyPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("it looks up the FortifyCredential", func() { + Context("fortify credential 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + clientMock.GetFunction = nil + config := v1alpha1.FortifyPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.FortifyPipelineConfigurationSpec{}, + Status: v1alpha1.FortifyPipelineConfigurationStatus{}, + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &config + 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 Fortify credential")) + }) + It("should return a reconcile result, and the error.", func() { + Expect(result).ToNot(BeNil()) + Expect(err).ToNot(BeNil()) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.FortifyPipelineConfiguration{}, &v1alpha1.FortifyPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("getting the secret from the Fortify Credential", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.FortifyCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.FortifyCredentialSpec{ + ServerURL: "https://example.com", + AccessTokenSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "username", + Optional: nil, + }, + }, + Status: v1alpha1.FortifyCredentialStatus{}, + } + config := v1alpha1.FortifyPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.FortifyPipelineConfigurationSpec{ + ProjectName: "valkyrie", + Language: "java", + FortifyCredentialName: "fortifyCred", + }, + Status: v1alpha1.FortifyPipelineConfigurationStatus{}, + } + + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.FortifyCredentialName, + }] = &fortifyCred + 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 fortify credential")) + }) + It("Should return a requeue result and an error", func() { + Expect(result).ToNot(BeNil()) + Expect(err).ToNot(BeNil()) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.FortifyPipelineConfiguration{}, &v1alpha1.FortifyPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Search projects in Fortify", func() { + Context("error searching project", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.FortifyCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.FortifyCredentialSpec{ + ServerURL: "https://test.com", + AccessTokenSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "accessToken", + Optional: nil, + }, + }, + Status: v1alpha1.FortifyCredentialStatus{}, + } + config := v1alpha1.FortifyPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.FortifyPipelineConfigurationSpec{ + ProjectName: "Valkyrie", + Language: "java", + FortifyCredentialName: "fortifyCred", + }, + Status: v1alpha1.FortifyPipelineConfigurationStatus{}, + } + + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.FortifyCredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.AccessTokenSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + //return error when trying to search for project + errorResponder := httpmock.NewErrorResponder(errors.New("Test Error")) + httpmock.RegisterResponder("GET", + "https://test.com/api/v1/projectVersions?limit=1&q=project.name%3AValkyrie", + errorResponder, + ) + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a requeue result and an error", func() { + Expect(result).ToNot(BeNil()) + Expect(err).ToNot(BeNil()) + Expect(err.Error()).To(ContainSubstring("Test Error")) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.FortifyPipelineConfiguration{}, &v1alpha1.FortifyPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Update existing project in Fortify", func() { + Context("error updating existing project", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.FortifyCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.FortifyCredentialSpec{ + ServerURL: "https://test.com", + AccessTokenSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "accessToken", + Optional: nil, + }, + }, + Status: v1alpha1.FortifyCredentialStatus{}, + } + config := v1alpha1.FortifyPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.FortifyPipelineConfigurationSpec{ + ProjectName: "Valkyrie", + Language: "java", + FortifyCredentialName: "fortifyCred", + }, + Status: v1alpha1.FortifyPipelineConfigurationStatus{}, + } + + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.FortifyCredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.AccessTokenSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + //mock api, search for project + searchResultResponse := &fortify.ProjectVersionsResponse{ + Data: []fortify.ProjectVersionResponse{ + { + ID: 88, + Name: "Valkyrie", + Description: "Created By Valkyrie", + CreatedBy: "Valkyrie", + CreationDate: "", + }, + }, + } + httpmock.RegisterResponder("GET", + "https://test.com/api/v1/projectVersions?limit=1&q=project.name%3AValkyrie", + httpmock.NewJsonResponderOrPanic(200, searchResultResponse), + ) + + errorResponder := httpmock.NewErrorResponder(errors.New("Test Error2")) + httpmock.RegisterResponder("POST", + "https://test.com/api/v1/bulk", + errorResponder, + ) + + //mock api, update failed + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a requeue result and an error", func() { + Expect(result).ToNot(BeNil()) + Expect(err).ToNot(BeNil()) + Expect(err.Error()).To(ContainSubstring("Test Error2")) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.FortifyPipelineConfiguration{}, &v1alpha1.FortifyPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Update existing project in Fortify", func() { + Context("successfully updated existing project", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.FortifyCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.FortifyCredentialSpec{ + ServerURL: "https://test.com", + AccessTokenSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "accessToken", + Optional: nil, + }, + }, + Status: v1alpha1.FortifyCredentialStatus{}, + } + config := v1alpha1.FortifyPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.FortifyPipelineConfigurationSpec{ + ProjectName: "Valkyrie", + Language: "java", + FortifyCredentialName: "fortifyCred", + }, + Status: v1alpha1.FortifyPipelineConfigurationStatus{}, + } + + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.FortifyCredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.AccessTokenSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + //mock api, search for project + searchResultResponse := &fortify.ProjectVersionsResponse{ + Data: []fortify.ProjectVersionResponse{ + { + ID: 88, + Name: "Valkyrie", + Description: "Created By Valkyrie", + CreatedBy: "Valkyrie", + CreationDate: "", + }, + }, + } + httpmock.RegisterResponder("GET", + "https://test.com/api/v1/projectVersions?limit=1&q=project.name%3AValkyrie", + httpmock.NewJsonResponderOrPanic(200, searchResultResponse), + ) + + httpmock.RegisterResponder("POST", + "https://test.com/api/v1/bulk", + httpmock.NewJsonResponderOrPanic(200, nil), + ) + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a result and no error", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).To(BeNil()) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.FortifyPipelineConfiguration{}, &v1alpha1.FortifyPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Creating new project in Fortify", func() { + Context("Failed to create new project", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.FortifyCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.FortifyCredentialSpec{ + ServerURL: "https://test.com", + AccessTokenSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "accessToken", + Optional: nil, + }, + }, + Status: v1alpha1.FortifyCredentialStatus{}, + } + config := v1alpha1.FortifyPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.FortifyPipelineConfigurationSpec{ + ProjectName: "Valkyrie", + Language: "java", + FortifyCredentialName: "fortifyCred", + }, + Status: v1alpha1.FortifyPipelineConfigurationStatus{}, + } + + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.FortifyCredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.AccessTokenSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + //mock api, search for project + searchResultResponse := &fortify.ProjectVersionsResponse{ + Data: []fortify.ProjectVersionResponse{}, + } + httpmock.RegisterResponder("GET", + "https://test.com/api/v1/projectVersions?limit=1&q=project.name%3AValkyrie", + httpmock.NewJsonResponderOrPanic(200, searchResultResponse), + ) + + errorResponder := httpmock.NewErrorResponder(errors.New("Test Error")) + httpmock.RegisterResponder("POST", + "https://test.com/api/v1/projectVersions", + errorResponder, + ) + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a result and an error", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).ToNot(BeNil()) + Expect(err.Error()).To(ContainSubstring("Test Error")) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.FortifyPipelineConfiguration{}, &v1alpha1.FortifyPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Creating new project in Fortify", func() { + Context("Failed to create new project", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.FortifyCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.FortifyCredentialSpec{ + ServerURL: "https://test.com", + AccessTokenSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "accessToken", + Optional: nil, + }, + }, + Status: v1alpha1.FortifyCredentialStatus{}, + } + config := v1alpha1.FortifyPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.FortifyPipelineConfigurationSpec{ + ProjectName: "Valkyrie", + Language: "java", + FortifyCredentialName: "fortifyCred", + }, + Status: v1alpha1.FortifyPipelineConfigurationStatus{}, + } + + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.FortifyCredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.AccessTokenSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + //mock api, search for project + searchResultResponse := &fortify.ProjectVersionsResponse{ + Data: []fortify.ProjectVersionResponse{}, + } + httpmock.RegisterResponder("GET", + "https://test.com/api/v1/projectVersions?limit=1&q=project.name%3AValkyrie", + httpmock.NewJsonResponderOrPanic(200, searchResultResponse), + ) + + //mock create api + createResposne := &fortify.ProjectVersionCreateResponse{ + Data: fortify.ProjectVersionResponse{ + ID: 88, + Name: "Valkyrie", + Description: "Created By Valkyrie", + CreatedBy: "Valkyrie", + CreationDate: "", + }, + } + httpmock.RegisterResponder("POST", + "https://test.com/api/v1/projectVersions", + httpmock.NewJsonResponderOrPanic(201, createResposne), + ) + + errorResponder := httpmock.NewErrorResponder(errors.New("Test Error2")) + httpmock.RegisterResponder("POST", + "https://test.com/api/v1/bulk", + errorResponder, + ) + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a result and an error", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).ToNot(BeNil()) + Expect(err.Error()).To(ContainSubstring("Test Error2")) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.FortifyPipelineConfiguration{}, &v1alpha1.FortifyPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Creating new project in Fortify", func() { + Context("Successfully create new project", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.FortifyCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.FortifyCredentialSpec{ + ServerURL: "https://test.com", + AccessTokenSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "accessToken", + Optional: nil, + }, + }, + Status: v1alpha1.FortifyCredentialStatus{}, + } + config := v1alpha1.FortifyPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.FortifyPipelineConfigurationSpec{ + ProjectName: "Valkyrie", + Language: "java", + FortifyCredentialName: "fortifyCred", + }, + Status: v1alpha1.FortifyPipelineConfigurationStatus{}, + } + + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.FortifyCredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.AccessTokenSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + //mock api, search for project + searchResultResponse := &fortify.ProjectVersionsResponse{ + Data: []fortify.ProjectVersionResponse{}, + } + httpmock.RegisterResponder("GET", + "https://test.com/api/v1/projectVersions?limit=1&q=project.name%3AValkyrie", + httpmock.NewJsonResponderOrPanic(200, searchResultResponse), + ) + + //mock create api + createResposne := &fortify.ProjectVersionCreateResponse{ + Data: fortify.ProjectVersionResponse{ + ID: 88, + Name: "Valkyrie", + Description: "Created By Valkyrie", + CreatedBy: "Valkyrie", + CreationDate: "", + }, + } + httpmock.RegisterResponder("POST", + "https://test.com/api/v1/projectVersions", + httpmock.NewJsonResponderOrPanic(201, createResposne), + ) + + httpmock.RegisterResponder("POST", + "https://test.com/api/v1/bulk", + httpmock.NewJsonResponderOrPanic(200, nil), + ) + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a result and no error", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).To(BeNil()) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.FortifyPipelineConfiguration{}, &v1alpha1.FortifyPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Delete project in Fortify", func() { + Context("Error delete project", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.FortifyCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.FortifyCredentialSpec{ + ServerURL: "https://test.com", + AccessTokenSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "accessToken", + Optional: nil, + }, + }, + Status: v1alpha1.FortifyCredentialStatus{}, + } + now := metav1.Now() + projectID := 88 + config := v1alpha1.FortifyPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{finalizerName}, + DeletionTimestamp: &now, + }, + Spec: v1alpha1.FortifyPipelineConfigurationSpec{ + ProjectName: "Valkyrie", + Language: "java", + FortifyCredentialName: "fortifyCred", + }, + Status: v1alpha1.FortifyPipelineConfigurationStatus{ + ProjectID: &projectID, + }, + } + + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.FortifyCredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.AccessTokenSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + //mock api, search for project + searchResultResponse := &fortify.ProjectVersionsResponse{ + Data: []fortify.ProjectVersionResponse{}, + } + httpmock.RegisterResponder("GET", + "https://test.com/api/v1/projectVersions?limit=1&q=project.name%3AValkyrie", + httpmock.NewJsonResponderOrPanic(200, searchResultResponse), + ) + + //mock create api + errorResponder := httpmock.NewErrorResponder(errors.New("Test Error")) + httpmock.RegisterResponder("DELETE", + "https://test.com/api/v1/projectVersions/88", + errorResponder, + ) + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a result and an error", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).ToNot(BeNil()) + Expect(err.Error()).To(ContainSubstring("Test Error")) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.FortifyPipelineConfiguration{}, &v1alpha1.FortifyPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Delete project in Fortify", func() { + Context("Error delete project", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.FortifyCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.FortifyCredentialSpec{ + ServerURL: "https://test.com", + AccessTokenSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "accessToken", + Optional: nil, + }, + }, + Status: v1alpha1.FortifyCredentialStatus{}, + } + now := metav1.Now() + projectID := 88 + config := v1alpha1.FortifyPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{finalizerName}, + DeletionTimestamp: &now, + }, + Spec: v1alpha1.FortifyPipelineConfigurationSpec{ + ProjectName: "Valkyrie", + Language: "java", + FortifyCredentialName: "fortifyCred", + }, + Status: v1alpha1.FortifyPipelineConfigurationStatus{ + ProjectID: &projectID, + }, + } + + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.FortifyCredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.AccessTokenSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + //mock create api + httpmock.RegisterResponder("DELETE", + "https://test.com/api/v1/projectVersions/88", + httpmock.NewJsonResponderOrPanic(200, nil), + ) + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a result and an error", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).To(BeNil()) + }) + }) + }) +}) diff --git a/controllers/fortify/mocks_test.go b/controllers/fortify/mocks_test.go new file mode 100755 index 0000000000000000000000000000000000000000..dfd0db9f23eadf50ff6a6f5e26768fe44fa1902c --- /dev/null +++ b/controllers/fortify/mocks_test.go @@ -0,0 +1,394 @@ +package fortify + +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" + "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 +} diff --git a/controllers/fortify/suite_test.go b/controllers/fortify/suite_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d6334afab65e2a0382e8721201dfab4a5dfbee71 --- /dev/null +++ b/controllers/fortify/suite_test.go @@ -0,0 +1,83 @@ +/* +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 fortify + +import ( + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + fortifyv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/fortify/v1alpha1" + //+kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecsWithDefaultAndCustomReporters(t, + "Controller Suite", + []Reporter{printer.NewlineReporter{}}) +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + } + + cfg, err := testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = fortifyv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + err = fortifyv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}, 60) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/controllers/gitlab/fortifypipelineconfiguration_controller.go b/controllers/gitlab/fortifypipelineconfiguration_controller.go deleted file mode 100644 index c559bd2f9b2b91283eee32f8639bc7a7ad011acb..0000000000000000000000000000000000000000 --- a/controllers/gitlab/fortifypipelineconfiguration_controller.go +++ /dev/null @@ -1,63 +0,0 @@ -/* -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" -) - -// FortifyPipelineConfigurationReconciler reconciles a FortifyPipelineConfiguration object -type FortifyPipelineConfigurationReconciler struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme -} - -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=fortifypipelineconfigurations,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=fortifypipelineconfigurations/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=fortifypipelineconfigurations/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. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the FortifyPipelineConfiguration 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 -func (r *FortifyPipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = r.Log.WithValues("fortifypipelineconfiguration", req.NamespacedName) - - // your logic here - - return ctrl.Result{}, nil -} - -// SetupWithManager sets up the controller with the Manager. -func (r *FortifyPipelineConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&gitlabv1alpha1.FortifyPipelineConfiguration{}). - Complete(r) -} diff --git a/controllers/gitlab/twistlockpipelineconfiguration_controller.go b/controllers/gitlab/twistlockpipelineconfiguration_controller.go deleted file mode 100644 index 0c93a550b66058d939336053ccba526b59acea2f..0000000000000000000000000000000000000000 --- a/controllers/gitlab/twistlockpipelineconfiguration_controller.go +++ /dev/null @@ -1,198 +0,0 @@ -/* -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" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "valkyrie.dso.mil/valkyrie-api/clients/twistlock" - - gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" - utils "valkyrie.dso.mil/valkyrie-api/controllers" -) - -const ( - twistlockServerURLEnv = "TWISTLOCK_SERVER_URL" - twistlockRegistryHostnameEnv = "TWISTLOCK_REGISTRY_HOSTNAME" - twistlockAPIUsernameEnv = "TWISTLOCK_API_USERNAME" - twistlockAPIPasswordEnv = "TWISTLOCK_API_PASSWORD" - twistlockRegistryCredentialIDEnv = "TWISTLOCK_REGISTRY_CREDENTIAL_ID" - registryVersion = "gcr" //"gcr" should be used in production. "harbor" for testing purpose - scannerCap = 5 - scannerOS = "linux" - scannerCount = 2 - finalizerName = "gitlab.valkyrie.dso.mil/twistlock_finalizer" -) - -// TwistlockPipelineConfigurationReconciler reconciles a TwistlockPipelineConfiguration object -type TwistlockPipelineConfigurationReconciler struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme -} - -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=twistlockpipelineconfigurations,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=twistlockpipelineconfigurations/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=twistlockpipelineconfigurations/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. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.2/pkg/reconcile -func (r *TwistlockPipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := r.Log.WithValues("twistlockpipelineconfiguration", req.NamespacedName) - - serverURL := utils.GetEnvOrExit(twistlockServerURLEnv) - registryHostname := utils.GetEnvOrExit(twistlockRegistryHostnameEnv) - apiUsername := utils.GetEnvOrExit(twistlockAPIUsernameEnv) - apiPassword := utils.GetEnvOrExit(twistlockAPIPasswordEnv) - registryCredentialID := utils.GetEnvOrExit(twistlockRegistryCredentialIDEnv) - - // your logic here - var twistlockConfig gitlabv1alpha1.TwistlockPipelineConfiguration - if err := r.Get(ctx, req.NamespacedName, &twistlockConfig); err != nil { - log.Error(err, "unable to fetch TwistlockPipelineConfiguration") - // 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) - } - - tlc := twistlock.NewClient(serverURL, apiUsername, apiPassword) - - // examine DeletionTimestamp to determine if object is under deletion - if twistlockConfig.ObjectMeta.DeletionTimestamp.IsZero() { - // The object is not being deleted, so if it does not have our finalizer, - // then lets add the finalizer and update the object. This is equivalent - // registering our finalizer. - if !utils.ContainsString(twistlockConfig.GetFinalizers(), finalizerName) { - controllerutil.AddFinalizer(&twistlockConfig, finalizerName) - if err := r.Update(ctx, &twistlockConfig); err != nil { - return ctrl.Result{}, err - } - } - } else { - // The object is being deleted - if utils.ContainsString(twistlockConfig.GetFinalizers(), finalizerName) { - // our finalizer is present, let's remove the reistry setting from twistlock. Twistlock doesn't support delete - // on registration setting, instead we need to recreate the full registry list without the item we want to remove - // then update the registry setting - //gets all the registry specifications from twistlock - log.Info("removing twistlock registry setting at: ", twistlockConfig.Spec.Repository) - registrySpecs, err := tlc.GetRegistrySpecs() - if err != nil { - return ctrl.Result{}, err - } - - newSpecs := make([]twistlock.Specification, 0) - for _, spec := range registrySpecs.Specifications { - //if spec already exists, update it - if spec.Repository == twistlockConfig.Spec.Repository { - continue - } else { - newSpecs = append(newSpecs, spec) - } - } - - specsRequest := &twistlock.RegistrySpecsUpdateRequest{Specifications: newSpecs} - err = tlc.UpdateRegistrySpec(specsRequest) - if err != nil { - return ctrl.Result{}, err - } - - // remove our finalizer from the list and update it. - controllerutil.RemoveFinalizer(&twistlockConfig, finalizerName) - if err = r.Update(ctx, &twistlockConfig); err != nil { - return ctrl.Result{}, err - } - } - - // Stop reconciliation as the item is being deleted - return ctrl.Result{}, nil - } - - //gets all the registry specifications from twistlock - registrySpecs, err := tlc.GetRegistrySpecs() - if err != nil { - return ctrl.Result{}, err - } - - //search the specsToUpdate - specsToUpdate := make([]twistlock.Specification, 0) - var needUpdate bool - for _, spec := range registrySpecs.Specifications { - //if spec already exists, update it - if spec.Repository == twistlockConfig.Spec.Repository { - newSpec := twistlock.Specification{ - Version: registryVersion, - Registry: registryHostname, - Repository: twistlockConfig.Spec.Repository, - Cap: scannerCap, - OS: scannerOS, - CredentialID: registryCredentialID, - Scanners: scannerCount, - //Collections: []string{"All"}, //needed if using "harbor" - } - specsToUpdate = append(specsToUpdate, newSpec) - needUpdate = true - } else { - specsToUpdate = append(specsToUpdate, spec) - } - } - - //if need to update the project, we update it - if needUpdate { - log.Info("updating projects") - specsRequest := &twistlock.RegistrySpecsUpdateRequest{Specifications: specsToUpdate} - err = tlc.UpdateRegistrySpec(specsRequest) - if err != nil { - return ctrl.Result{}, err - } - return ctrl.Result{}, nil - } - - //if spec doesn't exist, we create it - requestBody := &twistlock.AddRegisterSpecRequest{ - Version: registryVersion, - Registry: registryHostname, - Repository: twistlockConfig.Spec.Repository, - OS: scannerOS, - Scanner: scannerCount, - Cap: scannerCap, - Credential: twistlock.CredentialRequest{ID: registryCredentialID}, - //Collections: []string{"All"}, //needed if using "harbor" setting - } - log.Info("Register twistlock project") - if err = tlc.AddRegistrySpec(requestBody); err != nil { - log.Error(err, "error adding registry to twistlock") - return ctrl.Result{}, err - } - - return ctrl.Result{}, nil -} - -// SetupWithManager sets up the controller with the Manager. -func (r *TwistlockPipelineConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&gitlabv1alpha1.TwistlockPipelineConfiguration{}). - Complete(r) -} diff --git a/controllers/gitlab/twistlockpipelineconfiguration_controller_test.go b/controllers/gitlab/twistlockpipelineconfiguration_controller_test.go deleted file mode 100644 index 9bbcd22d91303307d5d3d6df80291c32eeda9027..0000000000000000000000000000000000000000 --- a/controllers/gitlab/twistlockpipelineconfiguration_controller_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package gitlab - -//var _ = Describe("Twistlock controller", func() { -// -// // Define utility constants for object names and testing timeouts/durations and intervals. -// const ( -// TwistlockConfigName = "test-twistlock" -// TwistlockConfigNamespace = "default" -// -// timeout = time.Second * 50 -// duration = time.Second * 10 -// interval = time.Millisecond * 250 -// ) -// -// Context("When updating Twistlock", func() { -// It("Should increase CronJob Status.Active count when new Jobs are created", func() { -// By("By creating a new CronJob") -// ctx := context.Background() -// twistlockConfig := &pipelinev1.TwistlockPipelineConfiguration{ -// TypeMeta: metav1.TypeMeta{ -// APIVersion: "gitlab.valkyrie.dso.mil/v1alpha1", -// Kind: "TwistlockPipelineConfiguration", -// }, -// ObjectMeta: metav1.ObjectMeta{ -// Name: TwistlockConfigName, -// Namespace: TwistlockConfigNamespace, -// }, -// Spec: pipelinev1.TwistlockPipelineConfigurationSpec{ -// //serverURL: "https://twistlock.bigbang.dev", -// //APIAccessSecretName: "twistlock-api-secret", -// //Registry: "registry.il2.dso.mil", -// Repository: "valkyrie-test", -// registryCredentialID: "Pipeline", -// }, -// } -// Expect(k8sClient.Create(ctx, twistlockConfig)).Should(Succeed()) -// -// twistlocConfigLookupKey := types.NamespacedName{Name: TwistlockConfigName, Namespace: TwistlockConfigNamespace} -// createdTwistlockConfig := &pipelinev1.TwistlockPipelineConfiguration{} -// -// // We'll need to retry getting this newly created CronJob, given that creation may not immediately happen. -// Eventually(func() bool { -// err := k8sClient.Get(ctx, twistlocConfigLookupKey, createdTwistlockConfig) -// if err != nil { -// return false -// } -// return true -// }, timeout, interval).Should(BeTrue()) -// // Let's make sure our Schedule string value was properly converted/handled. -// Expect(createdTwistlockConfig.Spec.registryCredentialID).Should(Equal("Pipeline")) -// }) -// }) -//}) diff --git a/controllers/twistlock/mocks_test.go b/controllers/twistlock/mocks_test.go new file mode 100755 index 0000000000000000000000000000000000000000..7f4ba1b612ee0d08f77a5e8e30f2c8bc1adfa041 --- /dev/null +++ b/controllers/twistlock/mocks_test.go @@ -0,0 +1,394 @@ +package twistlock + +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" + "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 +} diff --git a/controllers/twistlock/suite_test.go b/controllers/twistlock/suite_test.go new file mode 100644 index 0000000000000000000000000000000000000000..52ce90c9288deefa9eda2bb88a31400e4da67a22 --- /dev/null +++ b/controllers/twistlock/suite_test.go @@ -0,0 +1,83 @@ +/* +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 twistlock + +import ( + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + twistlockv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/twistlock/v1alpha1" + //+kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecsWithDefaultAndCustomReporters(t, + "Controller Suite", + []Reporter{printer.NewlineReporter{}}) +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + } + + cfg, err := testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = twistlockv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + err = twistlockv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}, 60) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/controllers/twistlock/twistlockcredential_controller.go b/controllers/twistlock/twistlockcredential_controller.go new file mode 100644 index 0000000000000000000000000000000000000000..4e423890f3a08c780777d05b5cc5e973a3d78b1c --- /dev/null +++ b/controllers/twistlock/twistlockcredential_controller.go @@ -0,0 +1,55 @@ +/* +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 twistlock + +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" + twistlockv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/twistlock/v1alpha1" +) + +// CredentialReconciler reconciles a TwistlockCredential object +type CredentialReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=twistlock.valkyrie.dso.mil,resources=twistlockcredentials,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=twistlock.valkyrie.dso.mil,resources=twistlockcredentials/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=twistlock.valkyrie.dso.mil,resources=twistlockcredentials/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. +func (r *CredentialReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = r.Log.WithValues("twistlockcredential", req.NamespacedName) + + // your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *CredentialReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&twistlockv1alpha1.TwistlockCredential{}). + Complete(r) +} diff --git a/controllers/twistlock/twistlockcredential_controller_test.go b/controllers/twistlock/twistlockcredential_controller_test.go new file mode 100755 index 0000000000000000000000000000000000000000..368cbf3f2a4c0f4ccdd8fe09b3a721982965214e --- /dev/null +++ b/controllers/twistlock/twistlockcredential_controller_test.go @@ -0,0 +1,65 @@ +package twistlock + +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/twistlock/v1alpha1" +) + +var _ = Describe("twistlockcredential_controller", func() { + Describe("Reconcile", func() { + logger := MockLogger{ + WithValuesKeysAndValues: make([]interface{}, 0), + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + sut := CredentialReconciler{ + 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("twistlockcredential")) + 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(&v1alpha1.TwistlockCredential{}, &v1alpha1.TwistlockCredentialList{}) + sut := CredentialReconciler{ + 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/twistlock/twistlockpipelineconfiguration_controller.go b/controllers/twistlock/twistlockpipelineconfiguration_controller.go new file mode 100644 index 0000000000000000000000000000000000000000..712dfeecf8b6001c79ae2c0872b3cb92fd76bdea --- /dev/null +++ b/controllers/twistlock/twistlockpipelineconfiguration_controller.go @@ -0,0 +1,311 @@ +/* +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 twistlock + +import ( + "context" + "github.com/go-logr/logr" + 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" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "valkyrie.dso.mil/valkyrie-api/clients/twistlock" + + twistlockv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/twistlock/v1alpha1" + utils "valkyrie.dso.mil/valkyrie-api/controllers" +) + +const ( + registryVersion = "gcr" //"gcr" should be used in production. "harbor" for testing purpose + scannerCap = 5 + scannerOS = "linux" + scannerCount = 2 + finalizerName = "gitlab.valkyrie.dso.mil/twistlock_finalizer" +) + +const ( + statusUpdateError = "error updating resource status" +) + +// PipelineConfigurationReconciler reconciles a TwistlockPipelineConfiguration object +type PipelineConfigurationReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=twistlockpipelineconfigurations,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=twistlockpipelineconfigurations/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=twistlockpipelineconfigurations/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. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.2/pkg/reconcile +func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) { + log := r.Log.WithValues("TwistlockPipelineConfiguration", req.NamespacedName) + log.Info("in Twistlock Controller") + + //default result + result = ctrl.Result{} + + // your logic here + var twistlockConfig twistlockv1alpha1.TwistlockPipelineConfiguration + if err = r.Get(ctx, req.NamespacedName, &twistlockConfig); err != nil { + log.Error(err, "unable to fetch TwistlockPipelineConfiguration") + err = client.IgnoreNotFound(err) + // 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 + } + + // Get the Twistlock Credential + var twistlockCredentialName = types.NamespacedName{ + Namespace: req.Namespace, + Name: twistlockConfig.Spec.CredentialName, + } + + var twistlockCredential twistlockv1alpha1.TwistlockCredential + if err = r.Get(ctx, twistlockCredentialName, &twistlockCredential); err != nil { + log.Error(err, "unable to fetch Twistlock credential") + twistlockConfig.Status.State = "twistlock credential not found." + if err2 := r.Status().Update(ctx, &twistlockConfig); err2 != nil { + log.Error(err2, statusUpdateError) + err = err2 + return + } + return + } + + //Get the username from secret + var usernameSecretName = types.NamespacedName{ + Namespace: req.Namespace, + Name: twistlockCredential.Spec.UsernameSecRef.Name, + } + + var usernameSecret v1.Secret + if err = r.Get(ctx, usernameSecretName, &usernameSecret); err != nil { + log.Error(err, "unable to fetch username secret from fortify credential") + twistlockConfig.Status.State = "username secret from Twistlock credential not found." + if err2 := r.Status().Update(ctx, &twistlockConfig); err2 != nil { + log.Error(err2, statusUpdateError) + err2 = err + return + } + return + } + username := string(usernameSecret.Data[twistlockCredential.Spec.UsernameSecRef.Key]) + + //Get pwd from secret + var pwdSecretName = types.NamespacedName{ + Namespace: req.Namespace, + Name: twistlockCredential.Spec.PasswordSecRef.Name, + } + + var pwdSecret v1.Secret + if err = r.Get(ctx, pwdSecretName, &pwdSecret); err != nil { + log.Error(err, "unable to fetch pwd secret from fortify credential") + twistlockConfig.Status.State = "pwd secret from Twistlock credential not found." + if err = r.Status().Update(ctx, &twistlockConfig); err != nil { + log.Error(err, statusUpdateError) + return + } + return + } + pwd := string(pwdSecret.Data[twistlockCredential.Spec.PasswordSecRef.Key]) + + //create Twistlock client for API calls + tlc := twistlock.NewClient(twistlockCredential.Spec.ServerURL, username, pwd) + + // examine DeletionTimestamp to determine if object is under deletion + if twistlockConfig.ObjectMeta.DeletionTimestamp.IsZero() { + err = r.handleCreateOrUpdate(ctx, twistlockConfig, log, &tlc) + } else { + err = r.handleDelete(ctx, twistlockConfig, log, &tlc) + } + return +} + +func (r *PipelineConfigurationReconciler) handleUpdate(ctx context.Context, twistlockConfig twistlockv1alpha1.TwistlockPipelineConfiguration, + log logr.Logger, tlc *twistlock.Client, specsToUpdate []twistlock.Specification) (err error) { + + log.Info("updating project for: " + twistlockConfig.Spec.Repository) + specsRequest := &twistlock.RegistrySpecsUpdateRequest{Specifications: specsToUpdate} + err = tlc.UpdateRegistrySpec(specsRequest) + if err != nil { + log.Error(err, "error updating registry specs") + return + } + + updateTime := metav1.Now() + twistlockConfig.Status.LastUpdatedTime = &updateTime + twistlockConfig.Status.State = "registry spec successfully updated" + if err = r.Status().Update(ctx, &twistlockConfig); err != nil { + log.Error(err, "error updating Twistlock resource") + return + } + log.Info("successfully updated registry spec: " + twistlockConfig.Spec.Repository) + return +} + +func (r *PipelineConfigurationReconciler) handleCrate(ctx context.Context, twistlockConfig twistlockv1alpha1.TwistlockPipelineConfiguration, + log logr.Logger, tlc *twistlock.Client) (err error) { + + //if spec doesn't exist, we create it + requestBody := &twistlock.AddRegisterSpecRequest{ + Version: registryVersion, + Registry: twistlockConfig.Spec.RegistryHostname, + Repository: twistlockConfig.Spec.Repository, + OS: scannerOS, + Scanner: scannerCount, + Cap: scannerCap, + Credential: twistlock.CredentialRequest{ID: twistlockConfig.Spec.RegistryCredentialID}, + //Collections: []string{"All"}, //needed if using "harbor" setting + } + log.Info("creating new Twistlock registry spec for : " + twistlockConfig.Spec.Repository) + if err = tlc.AddRegistrySpec(requestBody); err != nil { + log.Error(err, "error adding new registry to Twistlock") + return + } + + createdTime := metav1.Now() + twistlockConfig.Status.CreatedTime = &createdTime + twistlockConfig.Status.State = "registry spec successfully created" + if err = r.Status().Update(ctx, &twistlockConfig); err != nil { + log.Error(err, "error updating Twistlock resource") + return + } + log.Info("successfully created a new registry spec: " + twistlockConfig.Spec.Repository) + return +} + +func (r *PipelineConfigurationReconciler) handleCreateOrUpdate(ctx context.Context, twistlockConfig twistlockv1alpha1.TwistlockPipelineConfiguration, + log logr.Logger, tlc *twistlock.Client) (err error) { + // The object is not being deleted, so if it does not have our finalizer, + // then lets add the finalizer and update the object. This is equivalent + // registering our finalizer. + if !utils.ContainsString(twistlockConfig.GetFinalizers(), finalizerName) { + controllerutil.AddFinalizer(&twistlockConfig, finalizerName) + if err = r.Update(ctx, &twistlockConfig); err != nil { + log.Error(err, "error updating finalizer list") + return + } + } + + //gets all the registry specifications from Twistlock + var registrySpecs *twistlock.RegistrySpecsResponse + registrySpecs, err = tlc.GetRegistrySpecs() + if err != nil { + log.Error(err, "error getting registry specs from Twistlock") + return + } + + //search to see if spec already exists in Twistlock + specsToUpdate := make([]twistlock.Specification, 0) + var needUpdate bool + for _, spec := range registrySpecs.Specifications { + //if spec already exists, update it + if spec.Repository == twistlockConfig.Spec.Repository { + newSpec := twistlock.Specification{ + Version: registryVersion, + Registry: twistlockConfig.Spec.RegistryHostname, + Repository: twistlockConfig.Spec.Repository, + Cap: scannerCap, + OS: scannerOS, + CredentialID: twistlockConfig.Spec.RegistryCredentialID, + Scanners: scannerCount, + //Collections: []string{"All"}, //needed if using "harbor" + } + specsToUpdate = append(specsToUpdate, newSpec) + needUpdate = true + } else { + specsToUpdate = append(specsToUpdate, spec) + } + } + + //if need to update the project, we update it + if needUpdate { + err = r.handleUpdate(ctx, twistlockConfig, log, tlc, specsToUpdate) + } else { + err = r.handleCrate(ctx, twistlockConfig, log, tlc) + } + return +} + +func (r *PipelineConfigurationReconciler) handleDelete(ctx context.Context, twistlockConfig twistlockv1alpha1.TwistlockPipelineConfiguration, log logr.Logger, tlc *twistlock.Client) (err error) { + // The object is being deleted + if utils.ContainsString(twistlockConfig.GetFinalizers(), finalizerName) { + // our finalizer is present, let's remove the reistry setting from twistlock. Twistlock doesn't support delete + // on registration setting, instead we need to recreate the full registry list without the item we want to remove + // then update the registry setting + //gets all the registry specifications from twistlock + log.Info("removing Twistlock registry setting for: " + twistlockConfig.Spec.Repository) + var registrySpecs *twistlock.RegistrySpecsResponse + registrySpecs, err = tlc.GetRegistrySpecs() + if err != nil { + log.Error(err, "error getting registry specs from Twistlock") + return + } + + newSpecs, needToDelete := determineEndResult(registrySpecs, twistlockConfig) + if needToDelete { + specsRequest := &twistlock.RegistrySpecsUpdateRequest{Specifications: newSpecs} + err = tlc.UpdateRegistrySpec(specsRequest) + if err != nil { + log.Error(err, "error updating registries for Twistlock") + return + } + } + + // remove our finalizer from the list and update it. + controllerutil.RemoveFinalizer(&twistlockConfig, finalizerName) + if err = r.Update(ctx, &twistlockConfig); err != nil { + log.Error(err, "error updating Twistlock resource object") + return + } + } + return +} + +func determineEndResult(registrySpecs *twistlock.RegistrySpecsResponse, twistlockConfig twistlockv1alpha1.TwistlockPipelineConfiguration) (result []twistlock.Specification, needToDelete bool) { + newSpecs := make([]twistlock.Specification, 0) + for _, spec := range registrySpecs.Specifications { + //if spec already exists, then we need to delete/update it + if spec.Repository == twistlockConfig.Spec.Repository { + needToDelete = true + continue + } else { + newSpecs = append(newSpecs, spec) + } + } + return newSpecs, needToDelete +} + +// SetupWithManager sets up the controller with the Manager. +func (r *PipelineConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error { + pred := predicate.GenerationChangedPredicate{} + return ctrl.NewControllerManagedBy(mgr). + For(&twistlockv1alpha1.TwistlockPipelineConfiguration{}). + WithEventFilter(pred). + Complete(r) +} diff --git a/controllers/twistlock/twistlockpipelineconfiguration_controller_test.go b/controllers/twistlock/twistlockpipelineconfiguration_controller_test.go new file mode 100755 index 0000000000000000000000000000000000000000..3bf74d42a7d48905740393fab966c031556d902e --- /dev/null +++ b/controllers/twistlock/twistlockpipelineconfiguration_controller_test.go @@ -0,0 +1,1415 @@ +package twistlock + +import ( + "context" + "errors" + "github.com/jarcoal/httpmock" + . "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" + "valkyrie.dso.mil/valkyrie-api/apis/twistlock/v1alpha1" + "valkyrie.dso.mil/valkyrie-api/clients/twistlock" +) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.TwistlockPipelineConfiguration{}, &v1alpha1.TwistlockPipelineConfigurationList{}) + 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 := PipelineConfigurationReconciler{ + 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 TwistlockPipelineConfiguration")) + }) + 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 := v1alpha1.SchemeBuilder.Register(&v1alpha1.TwistlockPipelineConfiguration{}, &v1alpha1.TwistlockPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("it looks up the TwistlockCredential", func() { + Context("twistlock credential 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + clientMock.GetFunction = nil + config := v1alpha1.TwistlockPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockPipelineConfigurationSpec{}, + Status: v1alpha1.TwistlockPipelineConfigurationStatus{}, + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &config + 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 Twistlock credential")) + }) + It("should return a reconcile result, and the error.", func() { + Expect(result).ToNot(BeNil()) + Expect(err).ToNot(BeNil()) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.TwistlockPipelineConfiguration{}, &v1alpha1.TwistlockPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("getting the secret from the Fortify Credential", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.TwistlockCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockCredentialSpec{ + ServerURL: "https://example.com", + UsernameSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "username", + Optional: nil, + }, + PasswordSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "password", + Optional: nil, + }, + }, + Status: v1alpha1.TwistlockCredentialStatus{}, + } + config := v1alpha1.TwistlockPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockPipelineConfigurationSpec{ + Repository: "Valkyrie", + RegistryHostname: "repo1.dso.mil", + RegistryCredentialID: "pipeline", + CredentialName: "credName", + }, + Status: v1alpha1.TwistlockPipelineConfigurationStatus{}, + } + + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.CredentialName, + }] = &fortifyCred + 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 username secret from fortify credential")) + }) + It("Should return a requeue result and an error", func() { + Expect(result).ToNot(BeNil()) + Expect(err).ToNot(BeNil()) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.TwistlockPipelineConfiguration{}, &v1alpha1.TwistlockPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Search projects in Twistlock", func() { + Context("error searching Twistlock", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.TwistlockCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockCredentialSpec{ + ServerURL: "https://test", + UsernameSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "username", + Optional: nil, + }, + PasswordSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "password", + Optional: nil, + }, + }, + Status: v1alpha1.TwistlockCredentialStatus{}, + } + config := v1alpha1.TwistlockPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockPipelineConfigurationSpec{ + Repository: "Valkyrie", + RegistryHostname: "repo1.dso.mil", + RegistryCredentialID: "pipeline", + CredentialName: "credName", + }, + Status: v1alpha1.TwistlockPipelineConfigurationStatus{}, + } + + stringData := map[string]string{"password": "password", "username": "username"} + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.CredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.UsernameSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + httpmock.RegisterResponder("POST", + "https://test/api/v1/authenticate", + httpmock.NewJsonResponderOrPanic(200, &twistlock.TokenResponse{Token: "MyToken"}), + ) + httpmock.RegisterResponder("GET", + "https://test/api/v1/settings/registry", + httpmock.NewErrorResponder(errors.New("Unauthorized")), + ) + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a requeue result and an error", func() { + Expect(result).ToNot(BeNil()) + Expect(err).ToNot(BeNil()) + Expect(err.Error()).To(ContainSubstring("Unauthorized")) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.TwistlockPipelineConfiguration{}, &v1alpha1.TwistlockPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Update projects in Twistlock", func() { + Context("error updating Twistlock", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.TwistlockCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockCredentialSpec{ + ServerURL: "https://test", + UsernameSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "username", + Optional: nil, + }, + PasswordSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "password", + Optional: nil, + }, + }, + Status: v1alpha1.TwistlockCredentialStatus{}, + } + config := v1alpha1.TwistlockPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockPipelineConfigurationSpec{ + Repository: "Valkyrie", + RegistryHostname: "repo1.dso.mil", + RegistryCredentialID: "pipeline", + CredentialName: "credName", + }, + Status: v1alpha1.TwistlockPipelineConfigurationStatus{}, + } + + stringData := map[string]string{"password": "password", "username": "username"} + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.CredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.UsernameSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + httpmock.RegisterResponder("POST", + "https://test/api/v1/authenticate", + httpmock.NewJsonResponderOrPanic(200, &twistlock.TokenResponse{Token: "MyToken"}), + ) + httpmock.RegisterResponder("GET", + "https://test/api/v1/settings/registry", + httpmock.NewJsonResponderOrPanic(200, &twistlock.RegistrySpecsResponse{ + Specifications: []twistlock.Specification{ + { + Repository: "Valkyrie", + }, + }, + }), + ) + httpmock.RegisterResponder("PUT", + "https://test/api/v1/settings/registry", + httpmock.NewErrorResponder(errors.New("Test Error")), + ) + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a requeue result and an error", func() { + Expect(result).ToNot(BeNil()) + Expect(err).ToNot(BeNil()) + Expect(err.Error()).To(ContainSubstring("Test Error")) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.TwistlockPipelineConfiguration{}, &v1alpha1.TwistlockPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Create projects in Twistlock", func() { + Context("Error creating project in Twistlock", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.TwistlockCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockCredentialSpec{ + ServerURL: "https://test", + UsernameSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "username", + Optional: nil, + }, + PasswordSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "password", + Optional: nil, + }, + }, + Status: v1alpha1.TwistlockCredentialStatus{}, + } + config := v1alpha1.TwistlockPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockPipelineConfigurationSpec{ + Repository: "Valkyrie", + RegistryHostname: "repo1.dso.mil", + RegistryCredentialID: "pipeline", + CredentialName: "credName", + }, + Status: v1alpha1.TwistlockPipelineConfigurationStatus{}, + } + + stringData := map[string]string{"password": "password", "username": "username"} + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.CredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.UsernameSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + httpmock.RegisterResponder("POST", + "https://test/api/v1/authenticate", + httpmock.NewJsonResponderOrPanic(200, &twistlock.TokenResponse{Token: "MyToken"}), + ) + httpmock.RegisterResponder("GET", + "https://test/api/v1/settings/registry", + httpmock.NewJsonResponderOrPanic(200, &twistlock.RegistrySpecsResponse{}), + ) + httpmock.RegisterResponder("POST", + "https://test/api/v1/settings/registry", + httpmock.NewErrorResponder(errors.New("Test Error")), + ) + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a requeue result and an error", func() { + Expect(result).ToNot(BeNil()) + Expect(err).ToNot(BeNil()) + Expect(err.Error()).To(ContainSubstring("Test Error")) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.TwistlockPipelineConfiguration{}, &v1alpha1.TwistlockPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Delete project in Twistlock", func() { + Context("Error deleting project in Twistlock", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.TwistlockCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockCredentialSpec{ + ServerURL: "https://test", + UsernameSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "username", + Optional: nil, + }, + PasswordSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "password", + Optional: nil, + }, + }, + Status: v1alpha1.TwistlockCredentialStatus{}, + } + now := metav1.Now() + config := v1alpha1.TwistlockPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + DeletionTimestamp: &now, + Finalizers: []string{finalizerName}, + }, + Spec: v1alpha1.TwistlockPipelineConfigurationSpec{ + Repository: "Valkyrie", + RegistryHostname: "repo1.dso.mil", + RegistryCredentialID: "pipeline", + CredentialName: "credName", + }, + Status: v1alpha1.TwistlockPipelineConfigurationStatus{}, + } + + stringData := map[string]string{"password": "password", "username": "username"} + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.CredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.UsernameSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + httpmock.RegisterResponder("POST", + "https://test/api/v1/authenticate", + httpmock.NewJsonResponderOrPanic(200, &twistlock.TokenResponse{Token: "MyToken"}), + ) + httpmock.RegisterResponder("GET", + "https://test/api/v1/settings/registry", + httpmock.NewJsonResponderOrPanic(200, &twistlock.RegistrySpecsResponse{ + Specifications: []twistlock.Specification{ + { + Repository: "Valkyrie", + }, + }, + }), + ) + httpmock.RegisterResponder("PUT", + "https://test/api/v1/settings/registry", + httpmock.NewErrorResponder(errors.New("Test Error")), + ) + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a requeue result and an error", func() { + Expect(result).ToNot(BeNil()) + Expect(err).ToNot(BeNil()) + Expect(err.Error()).To(ContainSubstring("Test Error")) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.TwistlockPipelineConfiguration{}, &v1alpha1.TwistlockPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Delete projects in Twistlock", func() { + Context("Successfully deleted project in Twistlock", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.TwistlockCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockCredentialSpec{ + ServerURL: "https://test", + UsernameSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "username", + Optional: nil, + }, + PasswordSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "password", + Optional: nil, + }, + }, + Status: v1alpha1.TwistlockCredentialStatus{}, + } + now := metav1.Now() + config := v1alpha1.TwistlockPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + DeletionTimestamp: &now, + Finalizers: []string{finalizerName}, + }, + Spec: v1alpha1.TwistlockPipelineConfigurationSpec{ + Repository: "Valkyrie", + RegistryHostname: "repo1.dso.mil", + RegistryCredentialID: "pipeline", + CredentialName: "credName", + }, + Status: v1alpha1.TwistlockPipelineConfigurationStatus{}, + } + + stringData := map[string]string{"password": "password", "username": "username"} + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.CredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.UsernameSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + httpmock.RegisterResponder("POST", + "https://test/api/v1/authenticate", + httpmock.NewJsonResponderOrPanic(200, &twistlock.TokenResponse{Token: "MyToken"}), + ) + httpmock.RegisterResponder("GET", + "https://test/api/v1/settings/registry", + httpmock.NewJsonResponderOrPanic(200, &twistlock.RegistrySpecsResponse{ + Specifications: []twistlock.Specification{ + { + Repository: "Valkyrie", + }, + }, + }), + ) + httpmock.RegisterResponder("PUT", + "https://test/api/v1/settings/registry", + httpmock.NewJsonResponderOrPanic(200, &twistlock.RegistrySpecsResponse{}), + ) + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a requeue result and no error", func() { + Expect(result).ToNot(BeNil()) + Expect(err).To(BeNil()) + }) + }) + }) +}) + +/* +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.TwistlockPipelineConfiguration{}, &v1alpha1.TwistlockPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Update existing project in Fortify", func() { + Context("successfully updated existing project", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.TwistlockCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockCredentialSpec{ + ServerURL: "https://test.com", + UsernameSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "username", + Optional: nil, + }, + PasswordSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "password", + Optional: nil, + }, + }, + Status: v1alpha1.TwistlockCredentialStatus{}, + } + config := v1alpha1.TwistlockPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockPipelineConfigurationSpec{ + Repository: "Valkyrie", + RegistryHostname: "repo1.dso.mil", + RegistryCredentialID: "pipeline", + CredentialName: "credName", + }, + Status: v1alpha1.TwistlockPipelineConfigurationStatus{}, + } + + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.TwistlockCredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.AccessTokenSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + //mock api, search for project + searchResultResponse := &twistlock.ProjectVersionsResponse{ + Data: []twistlock.ProjectVersionResponse{ + { + ID: 88, + Name: "Valkyrie", + Description: "Created By Valkyrie", + CreatedBy: "Valkyrie", + CreationDate: "", + }, + }, + } + httpmock.RegisterResponder("GET", + "https://test.com/api/v1/projectVersions?limit=1&q=project.name%3AValkyrie", + httpmock.NewJsonResponderOrPanic(200, searchResultResponse), + ) + + httpmock.RegisterResponder("POST", + "https://test.com/api/v1/bulk", + httpmock.NewJsonResponderOrPanic(200, nil), + ) + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a result and no error", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).To(BeNil()) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.TwistlockPipelineConfiguration{}, &v1alpha1.TwistlockPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Creating new project in Fortify", func() { + Context("Failed to create new project", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.TwistlockCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockCredentialSpec{ + ServerURL: "https://test.com", + UsernameSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "username", + Optional: nil, + }, + PasswordSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "password", + Optional: nil, + }, + }, + Status: v1alpha1.TwistlockCredentialStatus{}, + } + config := v1alpha1.TwistlockPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockPipelineConfigurationSpec{ + Repository: "Valkyrie", + RegistryHostname: "repo1.dso.mil", + RegistryCredentialID: "pipeline", + CredentialName: "credName", + }, + Status: v1alpha1.TwistlockPipelineConfigurationStatus{}, + } + + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.TwistlockCredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.AccessTokenSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + //mock api, search for project + searchResultResponse := &twistlock.ProjectVersionsResponse{ + Data: []twistlock.ProjectVersionResponse{ + }, + } + httpmock.RegisterResponder("GET", + "https://test.com/api/v1/projectVersions?limit=1&q=project.name%3AValkyrie", + httpmock.NewJsonResponderOrPanic(200, searchResultResponse), + ) + + errorResponder := httpmock.NewErrorResponder(errors.New("Test Error")) + httpmock.RegisterResponder("POST", + "https://test.com/api/v1/projectVersions", + errorResponder, + ) + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a result and an error", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).ToNot(BeNil()) + Expect(err.Error()).To(ContainSubstring("Test Error")) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.TwistlockPipelineConfiguration{}, &v1alpha1.TwistlockPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Creating new project in Fortify", func() { + Context("Failed to create new project", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.TwistlockCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockCredentialSpec{ + ServerURL: "https://test.com", + UsernameSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "username", + Optional: nil, + }, + PasswordSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "password", + Optional: nil, + }, + }, + Status: v1alpha1.TwistlockCredentialStatus{}, + } + config := v1alpha1.TwistlockPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockPipelineConfigurationSpec{ + Repository: "Valkyrie", + RegistryHostname: "repo1.dso.mil", + RegistryCredentialID: "pipeline", + CredentialName: "credName", + }, + Status: v1alpha1.TwistlockPipelineConfigurationStatus{}, + } + + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.TwistlockCredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.AccessTokenSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + //mock api, search for project + searchResultResponse := &twistlock.ProjectVersionsResponse{ + Data: []twistlock.ProjectVersionResponse{ + }, + } + httpmock.RegisterResponder("GET", + "https://test.com/api/v1/projectVersions?limit=1&q=project.name%3AValkyrie", + httpmock.NewJsonResponderOrPanic(200, searchResultResponse), + ) + + //mock create api + createResposne := &twistlock.ProjectVersionCreateResponse{ + Data: twistlock.ProjectVersionResponse{ + ID: 88, + Name: "Valkyrie", + Description: "Created By Valkyrie", + CreatedBy: "Valkyrie", + CreationDate: "", + }, + } + httpmock.RegisterResponder("POST", + "https://test.com/api/v1/projectVersions", + httpmock.NewJsonResponderOrPanic(201, createResposne), + ) + + errorResponder := httpmock.NewErrorResponder(errors.New("Test Error2")) + httpmock.RegisterResponder("POST", + "https://test.com/api/v1/bulk", + errorResponder, + ) + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a result and an error", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).ToNot(BeNil()) + Expect(err.Error()).To(ContainSubstring("Test Error2")) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.TwistlockPipelineConfiguration{}, &v1alpha1.TwistlockPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Creating new project in Fortify", func() { + Context("Successfully create new project", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.TwistlockCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockCredentialSpec{ + ServerURL: "https://test.com", + UsernameSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "username", + Optional: nil, + }, + PasswordSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "password", + Optional: nil, + }, + }, + Status: v1alpha1.TwistlockCredentialStatus{}, + } + config := v1alpha1.TwistlockPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockPipelineConfigurationSpec{ + Repository: "Valkyrie", + RegistryHostname: "repo1.dso.mil", + RegistryCredentialID: "pipeline", + CredentialName: "credName", + }, + Status: v1alpha1.TwistlockPipelineConfigurationStatus{}, + } + + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.TwistlockCredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.AccessTokenSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + //mock api, search for project + searchResultResponse := &twistlock.ProjectVersionsResponse{ + Data: []twistlock.ProjectVersionResponse{ + }, + } + httpmock.RegisterResponder("GET", + "https://test.com/api/v1/projectVersions?limit=1&q=project.name%3AValkyrie", + httpmock.NewJsonResponderOrPanic(200, searchResultResponse), + ) + + //mock create api + createResposne := &twistlock.ProjectVersionCreateResponse{ + Data: twistlock.ProjectVersionResponse{ + ID: 88, + Name: "Valkyrie", + Description: "Created By Valkyrie", + CreatedBy: "Valkyrie", + CreationDate: "", + }, + } + httpmock.RegisterResponder("POST", + "https://test.com/api/v1/projectVersions", + httpmock.NewJsonResponderOrPanic(201, createResposne), + ) + + httpmock.RegisterResponder("POST", + "https://test.com/api/v1/bulk", + httpmock.NewJsonResponderOrPanic(200, nil), + ) + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a result and no error", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).To(BeNil()) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.TwistlockPipelineConfiguration{}, &v1alpha1.TwistlockPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Delete project in Fortify", func() { + Context("Error delete project", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.TwistlockCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockCredentialSpec{ + ServerURL: "https://test.com", + UsernameSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "username", + Optional: nil, + }, + PasswordSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "password", + Optional: nil, + }, + }, + Status: v1alpha1.TwistlockCredentialStatus{}, + } + now := metav1.Now() + projectID := 88 + config := v1alpha1.TwistlockPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{finalizerName}, + DeletionTimestamp: &now, + }, + Spec: v1alpha1.TwistlockPipelineConfigurationSpec{ + Repository: "Valkyrie", + RegistryHostname: "repo1.dso.mil", + RegistryCredentialID: "pipeline", + CredentialName: "credName", + }, + Status: v1alpha1.TwistlockPipelineConfigurationStatus{ + ProjectID: &projectID, + }, + } + + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.TwistlockCredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.AccessTokenSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + //mock api, search for project + searchResultResponse := &twistlock.ProjectVersionsResponse{ + Data: []twistlock.ProjectVersionResponse{ + }, + } + httpmock.RegisterResponder("GET", + "https://test.com/api/v1/projectVersions?limit=1&q=project.name%3AValkyrie", + httpmock.NewJsonResponderOrPanic(200, searchResultResponse), + ) + + //mock create api + errorResponder := httpmock.NewErrorResponder(errors.New("Test Error")) + httpmock.RegisterResponder("DELETE", + "https://test.com/api/v1/projectVersions/88", + errorResponder, + ) + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a result and an error", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).ToNot(BeNil()) + Expect(err.Error()).To(ContainSubstring("Test Error")) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.TwistlockPipelineConfiguration{}, &v1alpha1.TwistlockPipelineConfigurationList{}) + scheme, _ := builder.Build() + When("Delete project in Fortify", func() { + Context("Error delete project", 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 := PipelineConfigurationReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + fortifyCred := v1alpha1.TwistlockCredential{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: v1alpha1.TwistlockCredentialSpec{ + ServerURL: "https://test.com", + UsernameSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "username", + Optional: nil, + }, + PasswordSecRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secretName", + }, + Key: "password", + Optional: nil, + }, + }, + Status: v1alpha1.TwistlockCredentialStatus{}, + } + now := metav1.Now() + projectID := 88 + config := v1alpha1.TwistlockPipelineConfiguration{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{finalizerName}, + DeletionTimestamp: &now, + }, + Spec: v1alpha1.TwistlockPipelineConfigurationSpec{ + Repository: "Valkyrie", + RegistryHostname: "repo1.dso.mil", + RegistryCredentialID: "pipeline", + CredentialName: "credName", + }, + Status: v1alpha1.TwistlockPipelineConfigurationStatus{ + ProjectID: &projectID, + }, + } + + 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] = &config + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: config.Spec.TwistlockCredentialName, + }] = &fortifyCred + secretNamespacedName := types.NamespacedName{ + Namespace: "default", + Name: fortifyCred.Spec.AccessTokenSecRef.Name, + } + clientMock.expectedObjects[secretNamespacedName] = &secret + + //mock http client + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + //mock create api + httpmock.RegisterResponder("DELETE", + "https://test.com/api/v1/projectVersions/88", + httpmock.NewJsonResponderOrPanic(200, nil), + ) + + result, err := sut.Reconcile(contextMock, requestMock) + It("Should return a result and an error", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).To(BeNil()) + }) + }) + }) +}) +*/ diff --git a/driver.go b/driver.go index 3702e18056baa3c7d74242e60036ae1f04810754..a7f8ef8e401689cc59024d60ef7685976136bf16 100755 --- a/driver.go +++ b/driver.go @@ -10,7 +10,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" "valkyrie.dso.mil/valkyrie-api/controllers" customercontrollers "valkyrie.dso.mil/valkyrie-api/controllers/customer" + fortifycontrollers "valkyrie.dso.mil/valkyrie-api/controllers/fortify" gitlabcontrollers "valkyrie.dso.mil/valkyrie-api/controllers/gitlab" + twistlockcontrollers "valkyrie.dso.mil/valkyrie-api/controllers/twistlock" ) // driver is an interface that is a placeholder for the main logic of the program. We use this pattern to aid in testing of the main logic for the program. @@ -145,14 +147,24 @@ func (d driverImpl) instantiateControllers(mgr manager.Manager) []controllers.Ma Log: ctrl.Log.WithName("controllers").WithName("gitlab").WithName("Pipeline"), Scheme: mgr.GetScheme(), }, - &gitlabcontrollers.FortifyPipelineConfigurationReconciler{ + &fortifycontrollers.CredentialReconciler{ Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("gitlab").WithName("FortifyPipelineConfiguration"), + Log: ctrl.Log.WithName("controllers").WithName("fortify").WithName("FortifyCredential"), Scheme: mgr.GetScheme(), }, - &gitlabcontrollers.TwistlockPipelineConfigurationReconciler{ + &fortifycontrollers.PipelineConfigurationReconciler{ Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("gitlab").WithName("TwistlockPipelineConfiguration"), + Log: ctrl.Log.WithName("controllers").WithName("fortify").WithName("FortifyPipelineConfiguration"), + Scheme: mgr.GetScheme(), + }, + &twistlockcontrollers.PipelineConfigurationReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("twistlock").WithName("TwistlockPipelineConfiguration"), + Scheme: mgr.GetScheme(), + }, + &twistlockcontrollers.CredentialReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("twistlock").WithName("TwistlockCredential"), Scheme: mgr.GetScheme(), }, &gitlabcontrollers.SonarqubePipelineConfigurationReconciler{ diff --git a/driver_test.go b/driver_test.go index 8d29e3d19c8571fcb435d48265f2c9097c17a13f..ecc86e86df978da403437ad8973ca25fd2902178 100755 --- a/driver_test.go +++ b/driver_test.go @@ -10,7 +10,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" "valkyrie.dso.mil/valkyrie-api/controllers" "valkyrie.dso.mil/valkyrie-api/controllers/customer" + "valkyrie.dso.mil/valkyrie-api/controllers/fortify" "valkyrie.dso.mil/valkyrie-api/controllers/gitlab" + "valkyrie.dso.mil/valkyrie-api/controllers/twistlock" ) var _ = Describe("instantiateControllers", func() { @@ -33,29 +35,35 @@ var _ = Describe("instantiateControllers", func() { It("Should create a Pipeline Controller", func() { Expect(controllers[4]).To(BeAssignableToTypeOf(&gitlab.PipelineReconciler{})) }) + It("Should create a Fortify Credential Controller", func() { + Expect(controllers[5]).To(BeAssignableToTypeOf(&fortify.CredentialReconciler{})) + }) It("Should create a Fortify Configuration Controller", func() { - Expect(controllers[5]).To(BeAssignableToTypeOf(&gitlab.FortifyPipelineConfigurationReconciler{})) + Expect(controllers[6]).To(BeAssignableToTypeOf(&fortify.PipelineConfigurationReconciler{})) }) It("Should create a Twistlock Configuration Controller", func() { - Expect(controllers[6]).To(BeAssignableToTypeOf(&gitlab.TwistlockPipelineConfigurationReconciler{})) + Expect(controllers[7]).To(BeAssignableToTypeOf(&twistlock.PipelineConfigurationReconciler{})) + }) + It("Should create a Twistlock credential Controller", func() { + Expect(controllers[8]).To(BeAssignableToTypeOf(&twistlock.CredentialReconciler{})) }) It("Should create a SonarQube Configuration Controller", func() { - Expect(controllers[7]).To(BeAssignableToTypeOf(&gitlab.SonarqubePipelineConfigurationReconciler{})) + Expect(controllers[9]).To(BeAssignableToTypeOf(&gitlab.SonarqubePipelineConfigurationReconciler{})) }) It("Should create an SD Elements Configuration Controller", func() { - Expect(controllers[8]).To(BeAssignableToTypeOf(&gitlab.SdElementsPipelineConfigurationReconciler{})) + Expect(controllers[10]).To(BeAssignableToTypeOf(&gitlab.SdElementsPipelineConfigurationReconciler{})) }) It("Should create a Authorizing Official Controller", func() { - Expect(controllers[9]).To(BeAssignableToTypeOf(&customer.AuthorizingOfficialReconciler{})) + Expect(controllers[11]).To(BeAssignableToTypeOf(&customer.AuthorizingOfficialReconciler{})) }) It("Should create a System Owner Controller", func() { - Expect(controllers[10]).To(BeAssignableToTypeOf(&customer.SystemOwnerReconciler{})) + Expect(controllers[12]).To(BeAssignableToTypeOf(&customer.SystemOwnerReconciler{})) }) It("Should create a ChiefInformationSecurityOfficer Controller", func() { - Expect(controllers[11]).To(BeAssignableToTypeOf(&customer.ChiefInformationSecurityOfficerReconciler{})) + Expect(controllers[13]).To(BeAssignableToTypeOf(&customer.ChiefInformationSecurityOfficerReconciler{})) }) It("Should create a GitlabCredentials Controller", func() { - Expect(controllers[12]).To(BeAssignableToTypeOf(&gitlab.CredentialsReconciler{})) + Expect(controllers[14]).To(BeAssignableToTypeOf(&gitlab.CredentialsReconciler{})) }) }) }) diff --git a/integration-tests/fortify/fortify_api_adhoc_test.go b/integration-tests/fortify/fortify_api_adhoc_test.go new file mode 100644 index 0000000000000000000000000000000000000000..02f9c4bce771dc98c90937b24ab06613ccc09c57 --- /dev/null +++ b/integration-tests/fortify/fortify_api_adhoc_test.go @@ -0,0 +1,277 @@ +// +build integration + +package fortify + +import ( + "log" + "testing" + "valkyrie.dso.mil/valkyrie-api/clients/fortify" +) + +const ( + serverURL = "https://fortify.il2.dso.mil" + apiToken = "token" +) + +func TestRegisterProject(t *testing.T) { + client := fortify.NewClient(serverURL, apiToken) + projectVersions, err := client.SearchProjectVersions(&fortify.ProjectSearch{ + ResultLimit: 1, + ProjectName: "platform-one-devops-hello-pipeline-maven-world", + }) + if err != nil { + log.Printf("%v", err) + } + log.Printf("count = %d", len(projectVersions.Data)) +} + +func TestVersionCreate(t *testing.T) { + client := fortify.NewClient(serverURL, apiToken) + request := fortify.ProjectVersionCreateRequest{ + Name: "0.1", + Description: "Valkyrie Test", + Active: true, + Committed: false, + Project: fortify.ProjectRequest{ + Name: "Valkyrie3", + Description: "Created by Valkyrie", + IssueTemplateID: "Prioritized-HighRisk-Project-Template", + }, + IssueTemplateID: "Prioritized-HighRisk-Project-Template", + } + + resp, err := client.CreateProjectVersion(request) + if err != nil { + log.Printf("%v", err) + } + log.Println(resp.Data.ID) + +} + +func TestUpdateProject(t *testing.T) { + versionID := "841" + serverAPIURL := "https://fortify.il2.dso.mil/api/v1" + language := "javascript" + //make the request data + request := fortify.BulkUpdateRequest{Requests: []fortify.UpdateRequest{ + { + URI: serverAPIURL + "/projectVersions/" + versionID + "/attributes", + HTTPVerb: "PUT", + PostData: []fortify.PostDataRequest{ + { + AttributeDefinitionID: 1, + Values: []fortify.ValueRequest{ + { + GUID: "High", + }, + }, + }, + { + AttributeDefinitionID: 5, + Values: []fortify.ValueRequest{ + { + GUID: "Active", + }, + }, + }, + { + AttributeDefinitionID: 6, + Values: []fortify.ValueRequest{ + { + GUID: "Internal", + }, + }, + }, + { + AttributeDefinitionID: 7, + Values: []fortify.ValueRequest{ + { + GUID: "internalnetwork", + }, + }, + }, + { + AttributeDefinitionID: 8, + Values: []fortify.ValueRequest{ + { + GUID: "App", + }, + }, + }, + { + AttributeDefinitionID: 9, + Values: []fortify.ValueRequest{ + { + GUID: "NA", + }, + }, + }, + { + AttributeDefinitionID: 10, + Values: []fortify.ValueRequest{ + { + GUID: "WA", + }, + }, + }, + { + AttributeDefinitionID: 11, + Values: []fortify.ValueRequest{ + { + GUID: language, + }, + }, + }, + { + AttributeDefinitionID: 12, + Values: []fortify.ValueRequest{ + { + GUID: "None", + }, + }, + }, + }, + }, + { + URI: serverAPIURL + "/projectVersions/" + versionID + "/responsibilities", + HTTPVerb: "PUT", + PostData: []fortify.PostDataRequest{ + { + ResponsibilityGUID: "projectmanager", + }, + { + ResponsibilityGUID: "securitychampion", + }, + { + ResponsibilityGUID: "developmentmanager", + }, + { + ResponsibilityGUID: "superuser", + }, + }, + }, + { + URI: serverAPIURL + "/projectVersions/" + versionID + "?hideProgress=true", + HTTPVerb: "PUT", + PostData: fortify.ProjectVersionCreateRequest{ + Committed: true, + }, + }, + { + URI: serverAPIURL + "/projectVersions/" + versionID + "/resultProcessingRules", + HTTPVerb: "PUT", + PostData: []fortify.PostDataRequest{ + { + DisplayName: "Require approval if the Build Project is different between scans", + Identifier: "com.fortify.manager.BLL.processingrules.BuildProjectProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Check external metadata file versions in scan against versions on server.", + Identifier: "com.fortify.manager.BLL.processingrules.ExternalListVersionProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Require approval if file count differs by more than 10%", + Identifier: "com.fortify.manager.BLL.processingrules.FileCountProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Perform Force Instance ID migration on upload", + Identifier: "com.fortify.manager.BLL.processingrules.ForceMigrationProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Require approval if result has Fortify Java Annotations", + Identifier: "com.fortify.manager.BLL.processingrules.FortifyAnnotationsProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Require approval if line count differs by more than 10%", + Identifier: "com.fortify.manager.BLL.processingrules.LOCCountProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Automatically perform Instance ID migration on upload", + Identifier: "com.fortify.manager.BLL.processingrules.MigrationProcessingRule", + Enabled: true, + Displayable: true, + }, + { + DisplayName: "Require approval if the engine version of a scan is newer than the engine version of the previous scan", + Identifier: "com.fortify.manager.BLL.processingrules.NewerEngineVersionProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Ignore SCA scans performed in Quick Scan mode", + Identifier: "com.fortify.manager.BLL.processingrules.QuickScanProcessingRule", + Enabled: true, + Displayable: true, + }, + { + DisplayName: "Require approval if the rulepacks used in the scan do not match the rulepacks used in the previous scan", + Identifier: "com.fortify.manager.BLL.processingrules.RulePackVersionProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Require approval if SCA or WebInspect Agent scan does not have valid certification", + Identifier: "com.fortify.manager.BLL.processingrules.ValidCertificationProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Require approval if result has analysis warnings", + Identifier: "com.fortify.manager.BLL.processingrules.WarningProcessingRule", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Warn if audit information includes unknown custom tag", + Identifier: "com.fortify.manager.BLL.processingrules.UnknownOrDisallowedAuditedAttrChecker", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Require the issue audit permission to upload audited analysis files", + Identifier: "com.fortify.manager.BLL.processingrules.AuditedAnalysisRule", + Enabled: true, + Displayable: true, + }, + { + DisplayName: "Disallow upload of analysis results if there is one pending approval", + Identifier: "com.fortify.manager.BLL.processingrules.PendingApprovalChecker", + Enabled: false, + Displayable: true, + }, + { + DisplayName: "Disallow approval for processing if an earlier artifact requires approval", + Identifier: "com.fortify.manager.BLL.processingrules.VetoCascadingApprovalProcessingRule", + Enabled: false, + Displayable: true, + }, + }, + }, + }} + + client := fortify.NewClient(serverURL, apiToken) + err := client.BulkUpdate(request) + if err != nil { + log.Printf("error in update: %v", err) + } +} + +func TestDeleteProjectVersion(t *testing.T) { + client := fortify.NewClient(serverURL, apiToken) + err := client.DeleteProjectVersion(841) + if err != nil { + log.Printf("%v", err) + } +} diff --git a/main.go b/main.go index bfbf48acffc50d8ea2da51d407f4640ce9819acc..37919464dd1372861b03294850eabe23883b0ae2 100644 --- a/main.go +++ b/main.go @@ -29,8 +29,10 @@ import ( ctrl "sigs.k8s.io/controller-runtime" customerv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/customer/v1alpha1" + fortifyv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/fortify/v1alpha1" gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" sonarqubev1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/sonarqube/v1alpha1" + twistlockv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/twistlock/v1alpha1" //+kubebuilder:scaffold:imports ) @@ -48,6 +50,8 @@ func init() { utilruntime.Must(customerv1alpha1.AddToScheme(scheme)) utilruntime.Must(gitlabv1alpha1.AddToScheme(scheme)) utilruntime.Must(sonarqubev1alpha1.AddToScheme(scheme)) + utilruntime.Must(fortifyv1alpha1.AddToScheme(scheme)) + utilruntime.Must(twistlockv1alpha1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme }