From 5319f6bf1baa35927c296b8ae06ce8841b8f2a2f Mon Sep 17 00:00:00 2001 From: project_646_bot Date: Thu, 10 Jun 2021 14:38:35 -1000 Subject: [PATCH 01/16] add fortify api client --- .../fortifypipelineconfiguration_types.go | 7 +- clients/fortify/client.go | 104 +++++++ clients/fortify/client_test.go | 274 ++++++++++++++++++ clients/fortify/requests.go | 47 +++ clients/fortify/responses.go | 80 +++++ clients/utils.go | 33 ++- 6 files changed, 533 insertions(+), 12 deletions(-) create mode 100644 clients/fortify/client.go create mode 100644 clients/fortify/client_test.go create mode 100644 clients/fortify/requests.go create mode 100644 clients/fortify/responses.go diff --git a/apis/gitlab/v1alpha1/fortifypipelineconfiguration_types.go b/apis/gitlab/v1alpha1/fortifypipelineconfiguration_types.go index 79f8d26..fc7e637 100644 --- a/apis/gitlab/v1alpha1/fortifypipelineconfiguration_types.go +++ b/apis/gitlab/v1alpha1/fortifypipelineconfiguration_types.go @@ -20,7 +20,6 @@ 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. // FortifyPipelineConfigurationSpec defines the desired state of FortifyPipelineConfiguration @@ -28,8 +27,10 @@ type FortifyPipelineConfigurationSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired 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"` + // + ProjectName string `json:"projectName"` + Language string `json:"language"` + } // FortifyPipelineConfigurationStatus defines the observed state of FortifyPipelineConfiguration diff --git a/clients/fortify/client.go b/clients/fortify/client.go new file mode 100644 index 0000000..30c1f0a --- /dev/null +++ b/clients/fortify/client.go @@ -0,0 +1,104 @@ +package fortify + +import ( + "encoding/json" + "net/http" + "strconv" + "valkyrie.dso.mil/valkyrie-api/clients" +) + +const apiVersion = "/api/v1" +const authHeaderPrefix = "FortifyToken " + +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 { + client := clients.CreatHTTPClient() + + return &Client{ + serverAPIURL: serverURL + apiVersion, + apiToken: apiToken, + httpClient: client, + } +} + +func getAuthHeader(token string) string { + return authHeaderPrefix + token +} + +//GetProjectVersions 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) GetProjectVersions(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, 200) + 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 string) (err error) { + endpoint := c.serverAPIURL + "/projectVersions/" + 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 0000000..5687c49 --- /dev/null +++ b/clients/fortify/client_test.go @@ -0,0 +1,274 @@ +package fortify + +import ( + "log" + "testing" +) + +const ( + serverURL = "https://fortify.il2.dso.mil" + apiToken = "token" +) + +func TestRegisterProject(t *testing.T) { + client := NewClient(serverURL, apiToken) + projectVersions, err := client.GetProjectVersions(&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 := NewClient(serverURL, apiToken) + 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", + } + + 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 := BulkUpdateRequest{Requests: []UpdateRequest{ + { + URI: serverAPIURL + "/projectVersions/" + versionID + "/attributes", + HttpVerb: "PUT", + PostData: []PostDataRequest{ + { + AttributeDefinitionID: 1, + Values: []ValueRequest{ + { + GUID: "High", + }, + }, + }, + { + AttributeDefinitionID: 5, + Values: []ValueRequest{ + { + GUID: "Active", + }, + }, + }, + { + AttributeDefinitionID: 6, + Values: []ValueRequest{ + { + GUID: "Internal", + }, + }, + }, + { + AttributeDefinitionID: 7, + Values: []ValueRequest{ + { + GUID: "internalnetwork", + }, + }, + }, + { + AttributeDefinitionID: 8, + Values: []ValueRequest{ + { + GUID: "App", + }, + }, + }, + { + AttributeDefinitionID: 9, + Values: []ValueRequest{ + { + GUID: "NA", + }, + }, + }, + { + AttributeDefinitionID: 10, + Values: []ValueRequest{ + { + GUID: "WA", + }, + }, + }, + { + AttributeDefinitionID: 11, + Values: []ValueRequest{ + { + GUID: language, + }, + }, + }, + { + AttributeDefinitionID: 12, + Values: []ValueRequest{ + { + GUID: "None", + }, + }, + }, + }, + }, + { + URI: serverAPIURL + "/projectVersions/" + versionID + "/responsibilities", + HttpVerb: "PUT", + PostData: []PostDataRequest{ + { + ResponsibilityGUID: "projectmanager", + }, + { + ResponsibilityGUID: "securitychampion", + }, + { + ResponsibilityGUID: "developmentmanager", + }, + { + ResponsibilityGUID: "superuser", + }, + }, + }, + { + URI: serverAPIURL + "/projectVersions/" + versionID + "?hideProgress=true", + HttpVerb: "PUT", + PostData: ProjectVersionCreateRequest{ + Committed: true, + }, + }, + { + URI: serverAPIURL + "/projectVersions/" + versionID + "/resultProcessingRules", + HttpVerb: "PUT", + PostData: []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 := NewClient(serverURL, apiToken) + err := client.BulkUpdate(request) + if err != nil { + log.Printf("error in update: %v", err) + } +} + +func TestDeleteProjectVersion(t *testing.T) { + client := NewClient(serverURL, apiToken) + err := client.DeleteProjectVersion("841") + if err != nil { + log.Printf("%v", err) + } +} diff --git a/clients/fortify/requests.go b/clients/fortify/requests.go new file mode 100644 index 0000000..2fecc3c --- /dev/null +++ b/clients/fortify/requests.go @@ -0,0 +1,47 @@ +package fortify + +type ProjectSearch struct { + //set to 0 to retrieve everything back. default capped at 200. + ResultLimit int + ProjectName string +} + +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"` +} + +type ProjectRequest struct { + Name string `json:"name"` + Description string `json:"description"` + IssueTemplateID string `json:"issueTemplateId"` +} + +type BulkUpdateRequest struct { + Requests []UpdateRequest `json:"requests"` +} + +type UpdateRequest struct { + URI string `json:"uri"` + HttpVerb string `json:"httpVerb"` + PostData interface{} `json:"postData"` +} + +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"` +} + +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 0000000..a88fdf7 --- /dev/null +++ b/clients/fortify/responses.go @@ -0,0 +1,80 @@ +package fortify + +type ProjectVersionsResponse struct { + Data []ProjectVersionResponse `json:"data"` +} + +type ProjectVersionCreateResponse struct { + Data ProjectVersionResponse `json:"data"` +} + +//{ +// "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/utils.go b/clients/utils.go index 837f0a8..227e607 100644 --- a/clients/utils.go +++ b/clients/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 } - //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,23 @@ 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) } -- GitLab From 532236ebfac70c6b9375bff7fcb790a6c52acce2 Mon Sep 17 00:00:00 2001 From: project_646_bot Date: Thu, 10 Jun 2021 14:43:56 -1000 Subject: [PATCH 02/16] fix merge conflict with master --- clients/http_utils_test.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/clients/http_utils_test.go b/clients/http_utils_test.go index 695bc50..e541f2f 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 @@ -186,4 +188,4 @@ func TestGetPostPut(t *testing.T) { }) } httpmock.DeactivateAndReset() -} \ No newline at end of file +} -- GitLab From 8fdc58a8adbbf639620841e0e5663dcc9e042bd8 Mon Sep 17 00:00:00 2001 From: project_646_bot Date: Mon, 14 Jun 2021 14:22:46 -1000 Subject: [PATCH 03/16] feat: controller for Fortify --- PROJECT | 18 + README.md | 36 ++ .../v1alpha1/fortifycredential_types.go | 67 +++ .../fortifypipelineconfiguration_types.go | 25 +- apis/fortify/v1alpha1/groupversion_info.go | 36 ++ .../fortify/v1alpha1/zz_generated.deepcopy.go | 206 +++++++++ ...tifypipelineconfiguration_scaffold_test.go | 287 ------------ apis/gitlab/v1alpha1/project_types.go | 2 +- apis/gitlab/v1alpha1/zz_generated.deepcopy.go | 89 ---- clients/fortify/client.go | 20 +- clients/fortify/client_test.go | 100 ++-- clients/fortify/requests.go | 9 +- clients/fortify/responses.go | 4 +- clients/http_utils.go | 7 +- clients/twistlock/client.go | 2 +- .../customer.valkyrie.dso.mil_customers.yaml | 3 +- ...y.valkyrie.dso.mil_fortifycredentials.yaml | 78 ++++ ...so.mil_fortifypipelineconfigurations.yaml} | 46 +- .../bases/gitlab.valkyrie.dso.mil_groups.yaml | 3 +- .../gitlab.valkyrie.dso.mil_projects.yaml | 2 +- config/crd/kustomization.yaml | 5 +- .../cainjection_in_fortifycredentials.yaml | 7 + ...tion_in_fortifypipelineconfigurations.yaml | 2 +- .../webhook_in_fortifycredentials.yaml | 16 + ...hook_in_fortifypipelineconfigurations.yaml | 4 +- .../rbac/fortifycredential_editor_role.yaml | 24 + .../rbac/fortifycredential_viewer_role.yaml | 20 + ...tifypipelineconfiguration_editor_role.yaml | 4 +- ...tifypipelineconfiguration_viewer_role.yaml | 4 +- config/rbac/role.yaml | 32 +- .../fortify_v1alpha1_fortifycredential.yaml | 10 + ...v1alpha1_fortifypipelineconfiguration.yaml | 9 + ...v1alpha1_fortifypipelineconfiguration.yaml | 7 - .../fortifycredential_controller.go} | 29 +- ...fortifypipelineconfiguration_controller.go | 431 ++++++++++++++++++ controllers/fortify/suite_test.go | 83 ++++ controllers/utils_test.go | 2 +- driver.go | 10 +- driver_test.go | 18 +- go.mod | 1 + main.go | 7 +- 41 files changed, 1261 insertions(+), 504 deletions(-) create mode 100644 README.md create mode 100644 apis/fortify/v1alpha1/fortifycredential_types.go rename apis/{gitlab => fortify}/v1alpha1/fortifypipelineconfiguration_types.go (67%) create mode 100644 apis/fortify/v1alpha1/groupversion_info.go create mode 100644 apis/fortify/v1alpha1/zz_generated.deepcopy.go delete mode 100644 apis/gitlab/v1alpha1/fortifypipelineconfiguration_scaffold_test.go create mode 100644 config/crd/bases/fortify.valkyrie.dso.mil_fortifycredentials.yaml rename config/crd/bases/{gitlab.valkyrie.dso.mil_fortifypipelineconfigurations.yaml => fortify.valkyrie.dso.mil_fortifypipelineconfigurations.yaml} (55%) create mode 100644 config/crd/patches/cainjection_in_fortifycredentials.yaml create mode 100644 config/crd/patches/webhook_in_fortifycredentials.yaml create mode 100644 config/rbac/fortifycredential_editor_role.yaml create mode 100644 config/rbac/fortifycredential_viewer_role.yaml create mode 100644 config/samples/fortify_v1alpha1_fortifycredential.yaml create mode 100644 config/samples/fortify_v1alpha1_fortifypipelineconfiguration.yaml delete mode 100644 config/samples/gitlab_v1alpha1_fortifypipelineconfiguration.yaml rename controllers/{gitlab/fortifypipelineconfiguration_controller.go => fortify/fortifycredential_controller.go} (55%) create mode 100644 controllers/fortify/fortifypipelineconfiguration_controller.go create mode 100644 controllers/fortify/suite_test.go diff --git a/PROJECT b/PROJECT index f377d7f..8872c0b 100644 --- a/PROJECT +++ b/PROJECT @@ -122,4 +122,22 @@ 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 version: "3" diff --git a/README.md b/README.md new file mode 100644 index 0000000..41cffc4 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ + +# 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 \ No newline at end of file diff --git a/apis/fortify/v1alpha1/fortifycredential_types.go b/apis/fortify/v1alpha1/fortifycredential_types.go new file mode 100644 index 0000000..dcae0a1 --- /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_types.go b/apis/fortify/v1alpha1/fortifypipelineconfiguration_types.go similarity index 67% rename from apis/gitlab/v1alpha1/fortifypipelineconfiguration_types.go rename to apis/fortify/v1alpha1/fortifypipelineconfiguration_types.go index fc7e637..1c752d1 100644 --- a/apis/gitlab/v1alpha1/fortifypipelineconfiguration_types.go +++ b/apis/fortify/v1alpha1/fortifypipelineconfiguration_types.go @@ -20,26 +20,35 @@ 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. // 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"` + + //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 + //ProjectID is the ID of the project in Fortify + 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. + CreatedTime metav1.Time `json:"createdTime"` + + //LastUpdatedTime is the latest timestamp when the project was updated by the controller. + LastUpdatedTime metav1.Time `json:"lastUpdatedTime"` - // test Dummy is an example field of Project. Edit project_types.go to remove/update - Dummy string `json:"dummy,omitempty"` + //State is the current state + 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 0000000..e3415cd --- /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 0000000..3b603ca --- /dev/null +++ b/apis/fortify/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,206 @@ +// +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 + in.CreatedTime.DeepCopyInto(&out.CreatedTime) + in.LastUpdatedTime.DeepCopyInto(&out.LastUpdatedTime) +} + +// 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/fortifypipelineconfiguration_scaffold_test.go b/apis/gitlab/v1alpha1/fortifypipelineconfiguration_scaffold_test.go deleted file mode 100644 index ac9a2e9..0000000 --- a/apis/gitlab/v1alpha1/fortifypipelineconfiguration_scaffold_test.go +++ /dev/null @@ -1,287 +0,0 @@ -package v1alpha1 - -import ( - "reflect" - "testing" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// Reusable test variables -type testVarsFortifyPipelineConfiguration = struct { - testKind string - testApiversion string - testSpec string - testStatus string - - expectedKind string - expectedApiversion string - expectedSpec string - expectedStatus string - - testObject1 FortifyPipelineConfiguration - testObject2 FortifyPipelineConfiguration - - objectItems1 []FortifyPipelineConfiguration - objectList1 FortifyPipelineConfigurationList - - objectItems2 []FortifyPipelineConfiguration - objectList2 FortifyPipelineConfigurationList - - // leave scaffold Foo value for testing? - testObjectSpec1 FortifyPipelineConfigurationSpec - testObjectSpec2 FortifyPipelineConfigurationSpec - - // leave scaffold Foo value for testing? - testObjectStatus1 FortifyPipelineConfigurationStatus - testObjectStatus2 FortifyPipelineConfigurationStatus -} - -func initVarsFortifyPipelineConfiguration() testVarsFortifyPipelineConfiguration { - testVars := testVarsFortifyPipelineConfiguration{} - - 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 = FortifyPipelineConfiguration{TypeMeta: object1MetaType} - - var object2MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind2", APIVersion: "V99"} - testVars.testObject2 = FortifyPipelineConfiguration{TypeMeta: object2MetaType} - - var objectList1MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind_List", APIVersion: "V12"} - var objectItems1 []FortifyPipelineConfiguration = []FortifyPipelineConfiguration{testVars.testObject1, testVars.testObject2} - // test_object_list = FortifyPipelineConfigurationList(objectList1MetaType,nil,object_items) - testVars.objectList1 = FortifyPipelineConfigurationList{TypeMeta: objectList1MetaType, Items: objectItems1} - - var objectList2MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind_List", APIVersion: "V12"} - var objectItems2 []FortifyPipelineConfiguration = []FortifyPipelineConfiguration{testVars.testObject2} - // test_object_list = FortifyPipelineConfigurationList(objectList1MetaType,nil,object_items) - 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"} - - // leave scaffold Foo value for testing? - testVars.testObjectStatus1 = FortifyPipelineConfigurationStatus{Dummy: testVars.testStatus} - testVars.testObjectStatus2 = FortifyPipelineConfigurationStatus{Dummy: "other value"} - - return testVars -} - -func TestGroupVars_FortifyPipelineConfiguration(t *testing.T) { - - xType := reflect.TypeOf(GroupVersion) - // convert object type to string - got := xType.String() - want := "schema.GroupVersion" - - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} - -// Test Type called FortifyPipelineConfiguration -func TestTypes_FortifyPipelineConfiguration(t *testing.T) { - lTestVars := initVarsFortifyPipelineConfiguration() - want := lTestVars.expectedApiversion - - got := lTestVars.testObject1.APIVersion - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} - -// DeepCopy -func TestDeepCopy_DeepCopy_FortifyPipelineConfiguration(t *testing.T) { - lTestVars := initVarsFortifyPipelineConfiguration() - - 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 *FortifyPipelineConfiguration = nil - var val = nilTestPtr.DeepCopyObject() - if val != nil { - t.Errorf("got %s want %s", "not nil", "nil") - } - - t.Log("Success") -} - -func TestDeepCopy_DeepCopyInto_FortifyPipelineConfiguration(t *testing.T) { - lTestVars := initVarsFortifyPipelineConfiguration() - - lTestVars.testObject1.DeepCopyInto(&lTestVars.testObject2) - - got := lTestVars.testObject2.APIVersion - want := lTestVars.expectedApiversion - - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} - -func TestDeepCopy_DeepCopyObject_FortifyPipelineConfiguration(t *testing.T) { - lTestVars := initVarsFortifyPipelineConfiguration() - - newRuntimeObject := lTestVars.testObject1.DeepCopyObject() - newObject := newRuntimeObject.(*FortifyPipelineConfiguration) - got := newObject.APIVersion - want := lTestVars.expectedApiversion - - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} - -func TestDeepCopy_DeepCopyList_FortifyPipelineConfiguration(t *testing.T) { - lTestVars := initVarsFortifyPipelineConfiguration() - 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 *FortifyPipelineConfigurationList = nil - var val = nilTestPtr.DeepCopy() - if val != nil { - t.Errorf("got %s want %s", "not nil", "nil") - } - t.Log("Success") -} - -func TestDeepCopy_DeepCopyIntoList_FortifyPipelineConfiguration(t *testing.T) { - lTestVars := initVarsFortifyPipelineConfiguration() - - lTestVars.objectList1.DeepCopyInto(&lTestVars.objectList2) - - got := lTestVars.objectList2.Items[0].APIVersion - want := lTestVars.expectedApiversion - - if got != want { - t.Errorf("got %s want %s", got, want) - } - t.Log("Success") -} - -func TestDeepCopy_DeepCopyListObject_FortifyPipelineConfiguration(t *testing.T) { - lTestVars := initVarsFortifyPipelineConfiguration() - - newRuntimeObject := lTestVars.objectList1.DeepCopyObject() - newObject := newRuntimeObject.(*FortifyPipelineConfigurationList) - got := newObject.Items[0].APIVersion - want := lTestVars.expectedApiversion - - if got != want { - t.Errorf("got %s want %s", got, want) - } - - var nilTestPtr *FortifyPipelineConfigurationList = nil - var val = nilTestPtr.DeepCopyObject() - if val != nil { - t.Errorf("got %s want %s", "not nil", "nil") - } - - t.Log("Success") -} - -func TestDeepCopy_DeepCopySpec_FortifyPipelineConfiguration(t *testing.T) { - lTestVars := initVarsFortifyPipelineConfiguration() - - newObjectList := lTestVars.testObjectSpec1.DeepCopy() - - got := newObjectList.Dummy - want := lTestVars.expectedSpec - - if got != want { - t.Errorf("got %s want %s", got, want) - } - - var nilTestPtr *FortifyPipelineConfigurationSpec = nil - var val = nilTestPtr.DeepCopy() - if val != nil { - t.Errorf("got %s want %s", "not nil", "nil") - } - 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) { - lTestVars := initVarsFortifyPipelineConfiguration() - - newObjectStatus := lTestVars.testObjectStatus1.DeepCopy() - - got := newObjectStatus.Dummy - want := lTestVars.expectedStatus - - if got != want { - t.Errorf("got %s want %s", got, want) - } - - // a typed pointer set to nil - var nilTestPtr *FortifyPipelineConfigurationStatus = nil - var val = nilTestPtr.DeepCopy() - if val != nil { - t.Errorf("got %s want %s", "not nil", "nil") - } - - 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/project_types.go b/apis/gitlab/v1alpha1/project_types.go index 1a7c798..7c9aca4 100644 --- a/apis/gitlab/v1alpha1/project_types.go +++ b/apis/gitlab/v1alpha1/project_types.go @@ -59,7 +59,7 @@ type ProjectSpec struct { // ProjectStatus defines the observed state of Project type ProjectStatus struct { - // Url is the url for the project in GitLab + // ServerURL is the url for the project in GitLab URL string `json:"url"` // test Dummy is an example field of Project. Edit project_types.go to remove/update diff --git a/apis/gitlab/v1alpha1/zz_generated.deepcopy.go b/apis/gitlab/v1alpha1/zz_generated.deepcopy.go index 8265bc6..bc625f5 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 *Group) DeepCopyInto(out *Group) { *out = *in diff --git a/clients/fortify/client.go b/clients/fortify/client.go index 30c1f0a..3cd7e79 100644 --- a/clients/fortify/client.go +++ b/clients/fortify/client.go @@ -10,6 +10,7 @@ import ( 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 @@ -32,9 +33,14 @@ func getAuthHeader(token string) string { return authHeaderPrefix + token } -//GetProjectVersions gets all the project versions based on the search input. By default, max 200 entries are returned. +//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) GetProjectVersions(search *ProjectSearch) (projectVersions ProjectVersionsResponse, err error) { +func (c Client) SearchProjectVersions(search *ProjectSearch) (projectVersions ProjectVersionsResponse, err error) { // make the request var queryParams map[string]string @@ -61,14 +67,13 @@ func (c Client) GetProjectVersions(search *ProjectSearch) (projectVersions Proje return } - // CreateProjectVersion creates a new project version -func (c Client) CreateProjectVersion(request ProjectVersionCreateRequest) (projectVersion ProjectVersionCreateResponse, err error){ +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, 200) + resp, err = clients.Post(c.httpClient, endpoint, authHeaderValue, request, 201) if err != nil { return } @@ -80,8 +85,8 @@ func (c Client) CreateProjectVersion(request ProjectVersionCreateRequest) (proje } // DeleteProjectVersion deletes the project version for the given project version ID -func (c Client) DeleteProjectVersion(projectVersionID string) (err error) { - endpoint := c.serverAPIURL + "/projectVersions/" + projectVersionID +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 { @@ -101,4 +106,3 @@ func (c Client) BulkUpdate(request BulkUpdateRequest) (err error) { } return } - diff --git a/clients/fortify/client_test.go b/clients/fortify/client_test.go index 5687c49..b0346f5 100644 --- a/clients/fortify/client_test.go +++ b/clients/fortify/client_test.go @@ -7,12 +7,12 @@ import ( const ( serverURL = "https://fortify.il2.dso.mil" - apiToken = "token" + apiToken = "token" ) func TestRegisterProject(t *testing.T) { client := NewClient(serverURL, apiToken) - projectVersions, err := client.GetProjectVersions(&ProjectSearch{ + projectVersions, err := client.SearchProjectVersions(&ProjectSearch{ ResultLimit: 1, ProjectName: "platform-one-devops-hello-pipeline-maven-world", }) @@ -25,13 +25,13 @@ func TestRegisterProject(t *testing.T) { func TestVersionCreate(t *testing.T) { client := NewClient(serverURL, apiToken) request := ProjectVersionCreateRequest{ - Name: "0.1", - Description: "Valkyrie Test", - Active: true, - Committed: false, - Project: ProjectRequest{ - Name: "Valkyrie3", - Description: "Created by Valkyrie", + 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", @@ -52,8 +52,8 @@ func TestUpdateProject(t *testing.T) { //make the request data request := BulkUpdateRequest{Requests: []UpdateRequest{ { - URI: serverAPIURL + "/projectVersions/" + versionID + "/attributes", - HttpVerb: "PUT", + URI: serverAPIURL + "/projectVersions/" + versionID + "/attributes", + HTTPVerb: "PUT", PostData: []PostDataRequest{ { AttributeDefinitionID: 1, @@ -130,8 +130,8 @@ func TestUpdateProject(t *testing.T) { }, }, { - URI: serverAPIURL + "/projectVersions/" + versionID + "/responsibilities", - HttpVerb: "PUT", + URI: serverAPIURL + "/projectVersions/" + versionID + "/responsibilities", + HTTPVerb: "PUT", PostData: []PostDataRequest{ { ResponsibilityGUID: "projectmanager", @@ -148,110 +148,110 @@ func TestUpdateProject(t *testing.T) { }, }, { - URI: serverAPIURL + "/projectVersions/" + versionID + "?hideProgress=true", - HttpVerb: "PUT", + URI: serverAPIURL + "/projectVersions/" + versionID + "?hideProgress=true", + HTTPVerb: "PUT", PostData: ProjectVersionCreateRequest{ Committed: true, }, }, { - URI: serverAPIURL + "/projectVersions/" + versionID + "/resultProcessingRules", - HttpVerb: "PUT", + URI: serverAPIURL + "/projectVersions/" + versionID + "/resultProcessingRules", + HTTPVerb: "PUT", PostData: []PostDataRequest{ { DisplayName: "Require approval if the Build Project is different between scans", - Identifier: "com.fortify.manager.BLL.processingrules.BuildProjectProcessingRule", - Enabled: false, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + Identifier: "com.fortify.manager.BLL.processingrules.VetoCascadingApprovalProcessingRule", + Enabled: false, Displayable: true, }, }, @@ -267,7 +267,7 @@ func TestUpdateProject(t *testing.T) { func TestDeleteProjectVersion(t *testing.T) { client := NewClient(serverURL, apiToken) - err := client.DeleteProjectVersion("841") + err := client.DeleteProjectVersion(841) if err != nil { log.Printf("%v", err) } diff --git a/clients/fortify/requests.go b/clients/fortify/requests.go index 2fecc3c..c3df37d 100644 --- a/clients/fortify/requests.go +++ b/clients/fortify/requests.go @@ -1,11 +1,13 @@ 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"` @@ -15,22 +17,26 @@ type ProjectVersionCreateRequest struct { 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"` + 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"` @@ -42,6 +48,7 @@ type PostDataRequest struct { 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 index a88fdf7..6576901 100644 --- a/clients/fortify/responses.go +++ b/clients/fortify/responses.go @@ -1,13 +1,16 @@ 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": { @@ -77,4 +80,3 @@ type ProjectVersionResponse struct { CreatedBy string `json:"createdBy"` CreationDate string `json:"creationDate"` } - diff --git a/clients/http_utils.go b/clients/http_utils.go index 7421a4a..2c1d5b7 100644 --- a/clients/http_utils.go +++ b/clients/http_utils.go @@ -43,7 +43,7 @@ func callAPI(client *http.Client, method string, endpoint string, authHeaderValu requestBodyBytes, ) if err != nil { - return nil,err + return nil, err } req.Header.Set("Content-type", "application/json") if authHeaderValue != "" { @@ -62,7 +62,7 @@ func callAPI(client *http.Client, method string, endpoint string, authHeaderValu var resp *http.Response resp, err = client.Do(req) if err != nil { - return nil,err + return nil, err } if resp.StatusCode != successStatusCode { err = errors.New("unexpected http status code: " + resp.Status) @@ -74,7 +74,7 @@ func callAPI(client *http.Client, method string, endpoint string, authHeaderValu responseBody, err = ioutil.ReadAll(resp.Body) if err != nil { log.Println("error reading response body: ", err) - return nil,err + return nil, err } return responseBody, nil } @@ -97,7 +97,6 @@ func Put(client *http.Client, endpoint string, authHeaderValue string, requestDa 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/twistlock/client.go b/clients/twistlock/client.go index e1a5d37..3adf0f0 100644 --- a/clients/twistlock/client.go +++ b/clients/twistlock/client.go @@ -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 ff440d8..3ee8c22 100644 --- a/config/crd/bases/customer.valkyrie.dso.mil_customers.yaml +++ b/config/crd/bases/customer.valkyrie.dso.mil_customers.yaml @@ -135,7 +135,8 @@ spec: Edit project_types.go to remove/update type: string url: - description: Url is the url for the project in GitLab + description: ServerURL is the url for the project + in GitLab type: string required: - url 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 0000000..43fb1cc --- /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 55% rename from config/crd/bases/gitlab.valkyrie.dso.mil_fortifypipelineconfigurations.yaml rename to config/crd/bases/fortify.valkyrie.dso.mil_fortifypipelineconfigurations.yaml index 670c66b..51bd79a 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,19 +38,49 @@ 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 + required: + - createdTime + - lastUpdatedTime + - projectID + - state 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 8d0fe20..95ecba9 100644 --- a/config/crd/bases/gitlab.valkyrie.dso.mil_groups.yaml +++ b/config/crd/bases/gitlab.valkyrie.dso.mil_groups.yaml @@ -129,7 +129,8 @@ spec: Edit project_types.go to remove/update type: string url: - description: Url is the url for the project in GitLab + description: ServerURL is the url for the project in + GitLab type: string required: - url diff --git a/config/crd/bases/gitlab.valkyrie.dso.mil_projects.yaml b/config/crd/bases/gitlab.valkyrie.dso.mil_projects.yaml index 873a40a..7db3475 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: ServerURL is the url for the project in GitLab type: string required: - url diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index e4cfccc..b2611a0 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -7,7 +7,6 @@ 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 @@ -15,6 +14,8 @@ resources: - 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 #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -32,6 +33,7 @@ patchesStrategicMerge: #- patches/webhook_in_authorizingofficials.yaml #- patches/webhook_in_systemowners.yaml #- patches/webhook_in_chiefinformationsecurityofficers.yaml +#- patches/webhook_in_fortifycredentials.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. @@ -48,6 +50,7 @@ patchesStrategicMerge: #- patches/cainjection_in_authorizingofficials.yaml #- patches/cainjection_in_systemowners.yaml #- patches/cainjection_in_chiefinformationsecurityofficers.yaml +#- patches/cainjection_in_fortifycredentials.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 0000000..755edd8 --- /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 f3ba6e3..c08ab07 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/webhook_in_fortifycredentials.yaml b/config/crd/patches/webhook_in_fortifycredentials.yaml new file mode 100644 index 0000000..a7b8492 --- /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 242c581..a0fcf23 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/rbac/fortifycredential_editor_role.yaml b/config/rbac/fortifycredential_editor_role.yaml new file mode 100644 index 0000000..85e3e4b --- /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 0000000..67c184d --- /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 3875b32..56e90d9 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 3acb090..e4e61e8 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 3bd0088..21d7a1b 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -137,7 +137,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: @@ -149,13 +175,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: diff --git a/config/samples/fortify_v1alpha1_fortifycredential.yaml b/config/samples/fortify_v1alpha1_fortifycredential.yaml new file mode 100644 index 0000000..d251e73 --- /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 0000000..09af997 --- /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: "Valkyrie7" + language: "javascript" + credentialName: fortifycredential-sample + diff --git a/config/samples/gitlab_v1alpha1_fortifypipelineconfiguration.yaml b/config/samples/gitlab_v1alpha1_fortifypipelineconfiguration.yaml deleted file mode 100644 index 2f031ff..0000000 --- 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/controllers/gitlab/fortifypipelineconfiguration_controller.go b/controllers/fortify/fortifycredential_controller.go similarity index 55% rename from controllers/gitlab/fortifypipelineconfiguration_controller.go rename to controllers/fortify/fortifycredential_controller.go index c559bd2..3c2165e 100644 --- a/controllers/gitlab/fortifypipelineconfiguration_controller.go +++ b/controllers/fortify/fortifycredential_controller.go @@ -14,50 +14,49 @@ See the License for the specific language governing permissions and limitations under the License. */ -package gitlab +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" - gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" + fortifyv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/fortify/v1alpha1" ) -// FortifyPipelineConfigurationReconciler reconciles a FortifyPipelineConfiguration object -type FortifyPipelineConfigurationReconciler struct { +// FortifyCredentialReconciler reconciles a FortifyCredential object +type FortifyCredentialReconciler 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 +//+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. // TODO(user): Modify the Reconcile function to compare the state specified by -// the FortifyPipelineConfiguration object against the actual cluster state, and then +// the FortifyCredential 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) +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.8.3/pkg/reconcile +func (r *FortifyCredentialReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = r.Log.WithValues("gitlabcredentials", 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 { +func (r *FortifyCredentialReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&gitlabv1alpha1.FortifyPipelineConfiguration{}). + For(&fortifyv1alpha1.FortifyCredential{}). Complete(r) } diff --git a/controllers/fortify/fortifypipelineconfiguration_controller.go b/controllers/fortify/fortifypipelineconfiguration_controller.go new file mode 100644 index 0000000..3ce864f --- /dev/null +++ b/controllers/fortify/fortifypipelineconfiguration_controller.go @@ -0,0 +1,431 @@ +/* +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" + "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" + +// FortifyPipelineConfigurationReconciler reconciles a FortifyPipelineConfiguration object +type FortifyPipelineConfigurationReconciler 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. +// 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.8.3/pkg/reconcile +func (r *FortifyPipelineConfigurationReconciler) 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." + 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." + 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() { + // 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) + } + + //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 { + 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 + fortifyConfig.Status.LastUpdatedTime = metav1.Now() + fortifyConfig.Status.State = "project successfully updated" + log.Info("successfully updated Fortify project: " + fortifyConfig.Spec.ProjectName) + } else { + //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 + fortifyConfig.Status.CreatedTime = metav1.Now() + fortifyConfig.Status.State = "project successfully created" + log.Info("successfully created a new Fortify project: " + fortifyConfig.Spec.ProjectName) + } + + if err = r.Update(ctx, &fortifyConfig); err != nil { + log.Error(err, "error updating resource status") + return + } + return + + } else { + // 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) + err = fc.DeleteProjectVersion(fortifyConfig.Status.ProjectID) + if err != nil { + log.Error(err, "error deleting project from Fortify") + return + } + + // remove our finalizer from the list and update it. + controllerutil.RemoveFinalizer(&fortifyConfig, finalizerName) + if err = r.Update(ctx, &fortifyConfig); err != nil { + return + } + } + + // Stop reconciliation as the item is being deleted + return + } +} + +func createBulkUpdateRequest(serverAPIURL string, versionID string, language string) *fortify.BulkUpdateRequest { + return &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, + }, + }, + }, + }} +} + +// SetupWithManager sets up the controller with the Manager. +func (r *FortifyPipelineConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&fortifyv1alpha1.FortifyPipelineConfiguration{}). + Complete(r) +} diff --git a/controllers/fortify/suite_test.go b/controllers/fortify/suite_test.go new file mode 100644 index 0000000..d6334af --- /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/utils_test.go b/controllers/utils_test.go index 8af6798..2c67e76 100644 --- a/controllers/utils_test.go +++ b/controllers/utils_test.go @@ -1,6 +1,6 @@ package controllers -import "testing" +import "testing" import "os" func TestGetEnvOrExit(t *testing.T) { diff --git a/driver.go b/driver.go index 25f503d..d5e3153 100755 --- a/driver.go +++ b/driver.go @@ -10,6 +10,7 @@ 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" ) @@ -145,9 +146,14 @@ func (d driverImpl) instantiateControllers(mgr manager.Manager) []controllers.Ma Log: ctrl.Log.WithName("controllers").WithName("gitlab").WithName("Pipeline"), Scheme: mgr.GetScheme(), }, - &gitlabcontrollers.FortifyPipelineConfigurationReconciler{ + &fortifycontrollers.FortifyCredentialReconciler{ Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("gitlab").WithName("FortifyPipelineConfiguration"), + Log: ctrl.Log.WithName("controllers").WithName("fortify").WithName("FortifyCredential"), + Scheme: mgr.GetScheme(), + }, + &fortifycontrollers.FortifyPipelineConfigurationReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("fortify").WithName("FortifyPipelineConfiguration"), Scheme: mgr.GetScheme(), }, &gitlabcontrollers.TwistlockPipelineConfigurationReconciler{ diff --git a/driver_test.go b/driver_test.go index 00f7d27..a96f6c4 100755 --- a/driver_test.go +++ b/driver_test.go @@ -10,6 +10,7 @@ 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" ) @@ -33,26 +34,29 @@ 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.FortifyCredentialReconciler{})) + }) It("Should create a Fortify Configuration Controller", func() { - Expect(controllers[5]).To(BeAssignableToTypeOf(&gitlab.FortifyPipelineConfigurationReconciler{})) + Expect(controllers[6]).To(BeAssignableToTypeOf(&fortify.FortifyPipelineConfigurationReconciler{})) }) It("Should create a Twistlock Configuration Controller", func() { - Expect(controllers[6]).To(BeAssignableToTypeOf(&gitlab.TwistlockPipelineConfigurationReconciler{})) + Expect(controllers[7]).To(BeAssignableToTypeOf(&gitlab.TwistlockPipelineConfigurationReconciler{})) }) It("Should create a SonarQube Configuration Controller", func() { - Expect(controllers[7]).To(BeAssignableToTypeOf(&gitlab.SonarqubePipelineConfigurationReconciler{})) + Expect(controllers[8]).To(BeAssignableToTypeOf(&gitlab.SonarqubePipelineConfigurationReconciler{})) }) It("Should create an SD Elements Configuration Controller", func() { - Expect(controllers[8]).To(BeAssignableToTypeOf(&gitlab.SdElementsPipelineConfigurationReconciler{})) + Expect(controllers[9]).To(BeAssignableToTypeOf(&gitlab.SdElementsPipelineConfigurationReconciler{})) }) It("Should create a Authorizing Official Controller", func() { - Expect(controllers[9]).To(BeAssignableToTypeOf(&customer.AuthorizingOfficialReconciler{})) + Expect(controllers[10]).To(BeAssignableToTypeOf(&customer.AuthorizingOfficialReconciler{})) }) It("Should create a System Owner Controller", func() { - Expect(controllers[10]).To(BeAssignableToTypeOf(&customer.SystemOwnerReconciler{})) + Expect(controllers[11]).To(BeAssignableToTypeOf(&customer.SystemOwnerReconciler{})) }) It("Should create a ChiefInformationSecurityOfficer Controller", func() { - Expect(controllers[11]).To(BeAssignableToTypeOf(&customer.ChiefInformationSecurityOfficerReconciler{})) + Expect(controllers[12]).To(BeAssignableToTypeOf(&customer.ChiefInformationSecurityOfficerReconciler{})) }) }) }) diff --git a/go.mod b/go.mod index a14e729..67e792f 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.13.0 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect + k8s.io/api v0.19.2 k8s.io/apimachinery v0.19.2 k8s.io/client-go v0.19.2 sigs.k8s.io/controller-runtime v0.7.2 diff --git a/main.go b/main.go index 401b39b..2150ecb 100644 --- a/main.go +++ b/main.go @@ -17,15 +17,19 @@ limitations under the License. package main import ( + "os" + "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "os" + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" ctrl "sigs.k8s.io/controller-runtime" + customerv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/customer/v1alpha1" + 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" //+kubebuilder:scaffold:imports @@ -45,6 +49,7 @@ func init() { utilruntime.Must(customerv1alpha1.AddToScheme(scheme)) utilruntime.Must(gitlabv1alpha1.AddToScheme(scheme)) utilruntime.Must(sonarqubev1alpha1.AddToScheme(scheme)) + utilruntime.Must(fortifyv1alpha1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } -- GitLab From dfe44cec928b174ac45a8f0515c9f78acfdd6b95 Mon Sep 17 00:00:00 2001 From: project_646_bot Date: Tue, 15 Jun 2021 08:13:36 -1000 Subject: [PATCH 04/16] fix: only delete fortify project when we have the project ID --- .../fortifypipelineconfiguration_types.go | 2 +- .../fortifypipelineconfiguration_controller.go | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/apis/fortify/v1alpha1/fortifypipelineconfiguration_types.go b/apis/fortify/v1alpha1/fortifypipelineconfiguration_types.go index 1c752d1..18817c1 100644 --- a/apis/fortify/v1alpha1/fortifypipelineconfiguration_types.go +++ b/apis/fortify/v1alpha1/fortifypipelineconfiguration_types.go @@ -38,7 +38,7 @@ type FortifyPipelineConfigurationSpec struct { // FortifyPipelineConfigurationStatus defines the observed state of FortifyPipelineConfiguration type FortifyPipelineConfigurationStatus struct { //ProjectID is the ID of the project in Fortify - ProjectID int `json:"projectID"` + 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. diff --git a/controllers/fortify/fortifypipelineconfiguration_controller.go b/controllers/fortify/fortifypipelineconfiguration_controller.go index 3ce864f..7840c62 100644 --- a/controllers/fortify/fortifypipelineconfiguration_controller.go +++ b/controllers/fortify/fortifypipelineconfiguration_controller.go @@ -141,7 +141,7 @@ func (r *FortifyPipelineConfigurationReconciler) Reconcile(ctx context.Context, } //update status - fortifyConfig.Status.ProjectID = existingVersionID + fortifyConfig.Status.ProjectID = &existingVersionID fortifyConfig.Status.LastUpdatedTime = metav1.Now() fortifyConfig.Status.State = "project successfully updated" log.Info("successfully updated Fortify project: " + fortifyConfig.Spec.ProjectName) @@ -178,7 +178,7 @@ func (r *FortifyPipelineConfigurationReconciler) Reconcile(ctx context.Context, } //update status - fortifyConfig.Status.ProjectID = createResponse.Data.ID + fortifyConfig.Status.ProjectID = &createResponse.Data.ID fortifyConfig.Status.CreatedTime = metav1.Now() fortifyConfig.Status.State = "project successfully created" log.Info("successfully created a new Fortify project: " + fortifyConfig.Spec.ProjectName) @@ -195,10 +195,15 @@ func (r *FortifyPipelineConfigurationReconciler) Reconcile(ctx context.Context, 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) - err = fc.DeleteProjectVersion(fortifyConfig.Status.ProjectID) - if err != nil { - log.Error(err, "error deleting project from Fortify") - return + 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) } // remove our finalizer from the list and update it. -- GitLab From 05e537e443b23fcd175c448c94f4f9bea2c63f18 Mon Sep 17 00:00:00 2001 From: project_646_bot Date: Tue, 15 Jun 2021 08:55:06 -1000 Subject: [PATCH 05/16] Fix Merge Conflicts with Master --- .../fortify/v1alpha1/zz_generated.deepcopy.go | 5 + apis/gitlab/v1alpha1/zz_generated.deepcopy.go | 91 +++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/apis/fortify/v1alpha1/zz_generated.deepcopy.go b/apis/fortify/v1alpha1/zz_generated.deepcopy.go index 3b603ca..1d9bb62 100644 --- a/apis/fortify/v1alpha1/zz_generated.deepcopy.go +++ b/apis/fortify/v1alpha1/zz_generated.deepcopy.go @@ -191,6 +191,11 @@ func (in *FortifyPipelineConfigurationSpec) DeepCopy() *FortifyPipelineConfigura // 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 + } in.CreatedTime.DeepCopyInto(&out.CreatedTime) in.LastUpdatedTime.DeepCopyInto(&out.LastUpdatedTime) } diff --git a/apis/gitlab/v1alpha1/zz_generated.deepcopy.go b/apis/gitlab/v1alpha1/zz_generated.deepcopy.go index bc9b821..d939c18 100644 --- a/apis/gitlab/v1alpha1/zz_generated.deepcopy.go +++ b/apis/gitlab/v1alpha1/zz_generated.deepcopy.go @@ -24,6 +24,97 @@ 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 *GitlabCredentials) DeepCopyInto(out *GitlabCredentials) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitlabCredentials. +func (in *GitlabCredentials) DeepCopy() *GitlabCredentials { + if in == nil { + return nil + } + out := new(GitlabCredentials) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GitlabCredentials) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitlabCredentialsList) DeepCopyInto(out *GitlabCredentialsList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]GitlabCredentials, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitlabCredentialsList. +func (in *GitlabCredentialsList) DeepCopy() *GitlabCredentialsList { + if in == nil { + return nil + } + out := new(GitlabCredentialsList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GitlabCredentialsList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitlabCredentialsSpec) DeepCopyInto(out *GitlabCredentialsSpec) { + *out = *in + out.AccessToken = in.AccessToken +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitlabCredentialsSpec. +func (in *GitlabCredentialsSpec) DeepCopy() *GitlabCredentialsSpec { + if in == nil { + return nil + } + out := new(GitlabCredentialsSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitlabCredentialsStatus) DeepCopyInto(out *GitlabCredentialsStatus) { + *out = *in + in.LastUsedDate.DeepCopyInto(&out.LastUsedDate) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitlabCredentialsStatus. +func (in *GitlabCredentialsStatus) DeepCopy() *GitlabCredentialsStatus { + if in == nil { + return nil + } + out := new(GitlabCredentialsStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Group) DeepCopyInto(out *Group) { *out = *in -- GitLab From fd14ac0c121e594bfb5343d0c20115d3f5511852 Mon Sep 17 00:00:00 2001 From: project_646_bot Date: Wed, 16 Jun 2021 16:38:53 -1000 Subject: [PATCH 06/16] refactor twistlock controller to follow the pattern --- PROJECT | 18 ++ README.md | 42 ++- .../fortifypipelineconfiguration_types.go | 8 +- .../fortify/v1alpha1/zz_generated.deepcopy.go | 10 +- apis/gitlab/v1alpha1/zz_generated.deepcopy.go | 89 ------ apis/twistlock/v1alpha1/groupversion_info.go | 36 +++ .../v1alpha1/twistlockcredential_types.go | 66 ++++ ...lockpipelineconfiguration_scaffold_test.go | 8 +- .../twistlockpipelineconfiguration_types.go | 33 +- .../v1alpha1/zz_generated.deepcopy.go | 213 +++++++++++++ ...dso.mil_fortifypipelineconfigurations.yaml | 5 - ...valkyrie.dso.mil_twistlockcredentials.yaml | 98 ++++++ ....mil_twistlockpipelineconfigurations.yaml} | 39 ++- config/crd/kustomization.yaml | 5 +- .../cainjection_in_twistlockcredentials.yaml | 7 + ...on_in_twistlockpipelineconfigurations.yaml | 2 +- .../webhook_in_twistlockcredentials.yaml | 16 + ...ok_in_twistlockpipelineconfigurations.yaml | 2 +- config/rbac/role.yaml | 26 ++ ...lockpipelineconfiguration_editor_role.yaml | 4 +- ...lockpipelineconfiguration_viewer_role.yaml | 4 +- .../rbac/twistlockproperties_editor_role.yaml | 24 ++ .../rbac/twistlockproperties_viewer_role.yaml | 20 ++ ...alpha1_twistlockpipelineconfiguration.yaml | 8 - ...wistlock_v1alpha1_twistlockcredential.yaml | 12 + ...alpha1_twistlockpipelineconfiguration.yaml | 9 + ...fortifypipelineconfiguration_controller.go | 38 ++- ...istlockpipelineconfiguration_controller.go | 198 ------------ controllers/twistlock/suite_test.go | 83 ++++++ .../twistlockcredential_controller.go | 57 ++++ ...istlockpipelineconfiguration_controller.go | 281 ++++++++++++++++++ ...ckpipelineconfiguration_controller_test.go | 2 +- driver.go | 10 +- driver_test.go | 18 +- main.go | 2 + 35 files changed, 1134 insertions(+), 359 deletions(-) create mode 100644 apis/twistlock/v1alpha1/groupversion_info.go create mode 100644 apis/twistlock/v1alpha1/twistlockcredential_types.go rename apis/{gitlab => twistlock}/v1alpha1/twistlockpipelineconfiguration_scaffold_test.go (98%) rename apis/{gitlab => twistlock}/v1alpha1/twistlockpipelineconfiguration_types.go (67%) create mode 100644 apis/twistlock/v1alpha1/zz_generated.deepcopy.go create mode 100644 config/crd/bases/twistlock.valkyrie.dso.mil_twistlockcredentials.yaml rename config/crd/bases/{gitlab.valkyrie.dso.mil_twistlockpipelineconfigurations.yaml => twistlock.valkyrie.dso.mil_twistlockpipelineconfigurations.yaml} (61%) create mode 100644 config/crd/patches/cainjection_in_twistlockcredentials.yaml create mode 100644 config/crd/patches/webhook_in_twistlockcredentials.yaml create mode 100644 config/rbac/twistlockproperties_editor_role.yaml create mode 100644 config/rbac/twistlockproperties_viewer_role.yaml delete mode 100644 config/samples/gitlab_v1alpha1_twistlockpipelineconfiguration.yaml create mode 100644 config/samples/twistlock_v1alpha1_twistlockcredential.yaml create mode 100644 config/samples/twistlock_v1alpha1_twistlockpipelineconfiguration.yaml delete mode 100644 controllers/gitlab/twistlockpipelineconfiguration_controller.go create mode 100644 controllers/twistlock/suite_test.go create mode 100644 controllers/twistlock/twistlockcredential_controller.go create mode 100644 controllers/twistlock/twistlockpipelineconfiguration_controller.go rename controllers/{gitlab => twistlock}/twistlockpipelineconfiguration_controller_test.go (99%) diff --git a/PROJECT b/PROJECT index 4e6407c..a4d553c 100644 --- a/PROJECT +++ b/PROJECT @@ -149,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 41cffc4..b3baa90 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,43 @@ +# Twistlock Controller Setup +1) Create a secret that holds the username and password for Twistlock API +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: twistlock-secret +type: Opaque +data: + username: eW91cl90b2tlbg== + password: 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: username + passwordSecretRef: + name: twistlock-secret + key: password +``` +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. @@ -33,4 +73,4 @@ spec: language: "javascript" credentialName: fortifycredential-sample ``` -4) Apply the resources created above \ No newline at end of file +4) Apply the resources created above to the k8s cluster \ No newline at end of file diff --git a/apis/fortify/v1alpha1/fortifypipelineconfiguration_types.go b/apis/fortify/v1alpha1/fortifypipelineconfiguration_types.go index 18817c1..7a7aa34 100644 --- a/apis/fortify/v1alpha1/fortifypipelineconfiguration_types.go +++ b/apis/fortify/v1alpha1/fortifypipelineconfiguration_types.go @@ -38,16 +38,20 @@ type FortifyPipelineConfigurationSpec struct { // FortifyPipelineConfigurationStatus defines the observed state of FortifyPipelineConfiguration type FortifyPipelineConfigurationStatus struct { //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. - CreatedTime metav1.Time `json:"createdTime"` + // +optional + CreatedTime *metav1.Time `json:"createdTime"` //LastUpdatedTime is the latest timestamp when the project was updated by the controller. - LastUpdatedTime metav1.Time `json:"lastUpdatedTime"` + // +optional + LastUpdatedTime *metav1.Time `json:"lastUpdatedTime"` //State is the current state + // +optional State string `json:"state"` } diff --git a/apis/fortify/v1alpha1/zz_generated.deepcopy.go b/apis/fortify/v1alpha1/zz_generated.deepcopy.go index 1d9bb62..b4a4543 100644 --- a/apis/fortify/v1alpha1/zz_generated.deepcopy.go +++ b/apis/fortify/v1alpha1/zz_generated.deepcopy.go @@ -196,8 +196,14 @@ func (in *FortifyPipelineConfigurationStatus) DeepCopyInto(out *FortifyPipelineC *out = new(int) **out = **in } - in.CreatedTime.DeepCopyInto(&out.CreatedTime) - in.LastUpdatedTime.DeepCopyInto(&out.LastUpdatedTime) + 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. diff --git a/apis/gitlab/v1alpha1/zz_generated.deepcopy.go b/apis/gitlab/v1alpha1/zz_generated.deepcopy.go index d939c18..c706095 100644 --- a/apis/gitlab/v1alpha1/zz_generated.deepcopy.go +++ b/apis/gitlab/v1alpha1/zz_generated.deepcopy.go @@ -578,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 0000000..0f160f9 --- /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 0000000..6c5b618 --- /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 8158f88..8ea0e8d 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 31ff51d..fb2aae7 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 0000000..e5d05d4 --- /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/config/crd/bases/fortify.valkyrie.dso.mil_fortifypipelineconfigurations.yaml b/config/crd/bases/fortify.valkyrie.dso.mil_fortifypipelineconfigurations.yaml index 51bd79a..cca1db6 100644 --- a/config/crd/bases/fortify.valkyrie.dso.mil_fortifypipelineconfigurations.yaml +++ b/config/crd/bases/fortify.valkyrie.dso.mil_fortifypipelineconfigurations.yaml @@ -76,11 +76,6 @@ spec: state: description: State is the current state type: string - required: - - createdTime - - lastUpdatedTime - - projectID - - state type: object type: object served: true 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 0000000..116c2c8 --- /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 92b4e84..0376467 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 9d436b3..65f299b 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -7,7 +7,6 @@ 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_twistlockpipelineconfigurations.yaml - bases/gitlab.valkyrie.dso.mil_sonarqubepipelineconfigurations.yaml - bases/gitlab.valkyrie.dso.mil_sdelementspipelineconfigurations.yaml - bases/customer.valkyrie.dso.mil_authorizingofficials.yaml @@ -17,6 +16,8 @@ resources: - 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: @@ -36,6 +37,7 @@ patchesStrategicMerge: #- 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. @@ -54,6 +56,7 @@ patchesStrategicMerge: #- 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_twistlockcredentials.yaml b/config/crd/patches/cainjection_in_twistlockcredentials.yaml new file mode 100644 index 0000000..3718df2 --- /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 965e86e..7b1ebe9 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_twistlockcredentials.yaml b/config/crd/patches/webhook_in_twistlockcredentials.yaml new file mode 100644 index 0000000..472b2fc --- /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 3011b6e..584e4ad 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/role.yaml b/config/rbac/role.yaml index 72ad0ee..07ec3af 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -412,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 8e6736a..c324009 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 56dda60..6d98e6f 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 0000000..48b95c8 --- /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 0000000..b145728 --- /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/gitlab_v1alpha1_twistlockpipelineconfiguration.yaml b/config/samples/gitlab_v1alpha1_twistlockpipelineconfiguration.yaml deleted file mode 100644 index e67a8a2..0000000 --- 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 0000000..f8215a5 --- /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 0000000..0f0f9d4 --- /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/fortifypipelineconfiguration_controller.go b/controllers/fortify/fortifypipelineconfiguration_controller.go index 7840c62..53c6e0a 100644 --- a/controllers/fortify/fortifypipelineconfiguration_controller.go +++ b/controllers/fortify/fortifypipelineconfiguration_controller.go @@ -48,13 +48,6 @@ type FortifyPipelineConfigurationReconciler struct { // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the 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.8.3/pkg/reconcile func (r *FortifyPipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) { log := r.Log.WithValues("FortifyPipelineConfiguration", req.NamespacedName) log.Info("in Fortify Controller") @@ -82,6 +75,10 @@ func (r *FortifyPipelineConfigurationReconciler) Reconcile(ctx context.Context, 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 err = r.Status().Update(ctx, &fortifyConfig); err != nil { + log.Error(err, "error updating resource status") + return + } return } @@ -95,6 +92,10 @@ func (r *FortifyPipelineConfigurationReconciler) Reconcile(ctx context.Context, 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 err = r.Status().Update(ctx, &fortifyConfig); err != nil { + log.Error(err, "error updating resource status") + return + } return } @@ -109,6 +110,10 @@ func (r *FortifyPipelineConfigurationReconciler) Reconcile(ctx context.Context, // 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 updating finalizer list") + return + } } //check if project already exists in Fortify @@ -142,8 +147,13 @@ func (r *FortifyPipelineConfigurationReconciler) Reconcile(ctx context.Context, //update status fortifyConfig.Status.ProjectID = &existingVersionID - fortifyConfig.Status.LastUpdatedTime = metav1.Now() + 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, "error updating resource status") + return + } log.Info("successfully updated Fortify project: " + fortifyConfig.Spec.ProjectName) } else { //else we create a new one @@ -179,15 +189,16 @@ func (r *FortifyPipelineConfigurationReconciler) Reconcile(ctx context.Context, //update status fortifyConfig.Status.ProjectID = &createResponse.Data.ID - fortifyConfig.Status.CreatedTime = metav1.Now() + 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, "error updating resource status") + return + } log.Info("successfully created a new Fortify project: " + fortifyConfig.Spec.ProjectName) } - if err = r.Update(ctx, &fortifyConfig); err != nil { - log.Error(err, "error updating resource status") - return - } return } else { @@ -209,6 +220,7 @@ func (r *FortifyPipelineConfigurationReconciler) Reconcile(ctx context.Context, // 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 updating Fortify resource status") return } } diff --git a/controllers/gitlab/twistlockpipelineconfiguration_controller.go b/controllers/gitlab/twistlockpipelineconfiguration_controller.go deleted file mode 100644 index 0c93a55..0000000 --- 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/twistlock/suite_test.go b/controllers/twistlock/suite_test.go new file mode 100644 index 0000000..52ce90c --- /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 0000000..429d53e --- /dev/null +++ b/controllers/twistlock/twistlockcredential_controller.go @@ -0,0 +1,57 @@ +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package 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" + "sigs.k8s.io/controller-runtime/pkg/log" + + twistlockv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/twistlock/v1alpha1" +) + +// TwistlockCredentialReconciler reconciles a TwistlockCredential object +type TwistlockCredentialReconciler 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 *TwistlockCredentialReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *TwistlockCredentialReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&twistlockv1alpha1.TwistlockCredential{}). + Complete(r) +} diff --git a/controllers/twistlock/twistlockpipelineconfiguration_controller.go b/controllers/twistlock/twistlockpipelineconfiguration_controller.go new file mode 100644 index 0000000..9b5f4c7 --- /dev/null +++ b/controllers/twistlock/twistlockpipelineconfiguration_controller.go @@ -0,0 +1,281 @@ +/* +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" +) + +// 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) (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 err = r.Status().Update(ctx, &twistlockConfig); err != nil { + log.Error(err, "error updating resource status") + 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 err = r.Status().Update(ctx, &twistlockConfig); err != nil { + log.Error(err, "error updating resource status") + return + } + return + } + username := string(usernameSecret.Data[twistlockCredential.Spec.UsernameSecRef.Key]) + + //Get the password from secret + var passwordSecretName = types.NamespacedName{ + Namespace: req.Namespace, + Name: twistlockCredential.Spec.PasswordSecRef.Name, + } + + var passwordSecret v1.Secret + if err = r.Get(ctx, passwordSecretName, &passwordSecret); err != nil { + log.Error(err, "unable to fetch password secret from fortify credential") + twistlockConfig.Status.State = "password secret from Twistlock credential not found." + if err = r.Status().Update(ctx, &twistlockConfig); err != nil { + log.Error(err, "error updating resource status") + return + } + return + } + password := string(passwordSecret.Data[twistlockCredential.Spec.PasswordSecRef.Key]) + + //create Twistlock client for API calls + tlc := twistlock.NewClient(twistlockCredential.Spec.ServerURL, username, password) + + // 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 { + 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 { + 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) + } else { + //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 + } 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 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 := make([]twistlock.Specification, 0) + var needToDelete bool + 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) + } + } + + 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 + } + } + + // Stop reconciliation as the item is being deleted + return + } +} + +// SetupWithManager sets up the controller with the Manager. +func (r *TwistlockPipelineConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error { + pred := predicate.GenerationChangedPredicate{} + return ctrl.NewControllerManagedBy(mgr). + For(&twistlockv1alpha1.TwistlockPipelineConfiguration{}). + WithEventFilter(pred). + Complete(r) +} diff --git a/controllers/gitlab/twistlockpipelineconfiguration_controller_test.go b/controllers/twistlock/twistlockpipelineconfiguration_controller_test.go similarity index 99% rename from controllers/gitlab/twistlockpipelineconfiguration_controller_test.go rename to controllers/twistlock/twistlockpipelineconfiguration_controller_test.go index 9bbcd22..0f62215 100644 --- a/controllers/gitlab/twistlockpipelineconfiguration_controller_test.go +++ b/controllers/twistlock/twistlockpipelineconfiguration_controller_test.go @@ -1,4 +1,4 @@ -package gitlab +package twistlock //var _ = Describe("Twistlock controller", func() { // diff --git a/driver.go b/driver.go index 23453da..723fa54 100755 --- a/driver.go +++ b/driver.go @@ -12,6 +12,7 @@ import ( 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. @@ -156,9 +157,14 @@ func (d driverImpl) instantiateControllers(mgr manager.Manager) []controllers.Ma Log: ctrl.Log.WithName("controllers").WithName("fortify").WithName("FortifyPipelineConfiguration"), Scheme: mgr.GetScheme(), }, - &gitlabcontrollers.TwistlockPipelineConfigurationReconciler{ + &twistlockcontrollers.TwistlockPipelineConfigurationReconciler{ Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("gitlab").WithName("TwistlockPipelineConfiguration"), + Log: ctrl.Log.WithName("controllers").WithName("twistlock").WithName("TwistlockPipelineConfiguration"), + Scheme: mgr.GetScheme(), + }, + &twistlockcontrollers.TwistlockCredentialReconciler{ + 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 11b7460..6de7b40 100755 --- a/driver_test.go +++ b/driver_test.go @@ -12,6 +12,7 @@ import ( "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() { @@ -41,25 +42,28 @@ var _ = Describe("instantiateControllers", func() { Expect(controllers[6]).To(BeAssignableToTypeOf(&fortify.FortifyPipelineConfigurationReconciler{})) }) It("Should create a Twistlock Configuration Controller", func() { - Expect(controllers[7]).To(BeAssignableToTypeOf(&gitlab.TwistlockPipelineConfigurationReconciler{})) + Expect(controllers[7]).To(BeAssignableToTypeOf(&twistlock.TwistlockPipelineConfigurationReconciler{})) + }) + It("Should create a Twistlock credential Controller", func() { + Expect(controllers[8]).To(BeAssignableToTypeOf(&twistlock.TwistlockCredentialReconciler{})) }) It("Should create a SonarQube Configuration Controller", func() { - Expect(controllers[8]).To(BeAssignableToTypeOf(&gitlab.SonarqubePipelineConfigurationReconciler{})) + Expect(controllers[9]).To(BeAssignableToTypeOf(&gitlab.SonarqubePipelineConfigurationReconciler{})) }) It("Should create an SD Elements Configuration Controller", func() { - Expect(controllers[9]).To(BeAssignableToTypeOf(&gitlab.SdElementsPipelineConfigurationReconciler{})) + Expect(controllers[10]).To(BeAssignableToTypeOf(&gitlab.SdElementsPipelineConfigurationReconciler{})) }) It("Should create a Authorizing Official Controller", func() { - Expect(controllers[10]).To(BeAssignableToTypeOf(&customer.AuthorizingOfficialReconciler{})) + Expect(controllers[11]).To(BeAssignableToTypeOf(&customer.AuthorizingOfficialReconciler{})) }) It("Should create a System Owner Controller", func() { - Expect(controllers[11]).To(BeAssignableToTypeOf(&customer.SystemOwnerReconciler{})) + Expect(controllers[12]).To(BeAssignableToTypeOf(&customer.SystemOwnerReconciler{})) }) It("Should create a ChiefInformationSecurityOfficer Controller", func() { - Expect(controllers[12]).To(BeAssignableToTypeOf(&customer.ChiefInformationSecurityOfficerReconciler{})) + Expect(controllers[13]).To(BeAssignableToTypeOf(&customer.ChiefInformationSecurityOfficerReconciler{})) }) It("Should create a GitlabCredentials Controller", func() { - Expect(controllers[12]).To(BeAssignableToTypeOf(&gitlab.GitlabCredentialsReconciler{})) + Expect(controllers[14]).To(BeAssignableToTypeOf(&gitlab.GitlabCredentialsReconciler{})) }) }) }) diff --git a/main.go b/main.go index 2150ecb..3791946 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,7 @@ import ( 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 ) @@ -50,6 +51,7 @@ func init() { utilruntime.Must(gitlabv1alpha1.AddToScheme(scheme)) utilruntime.Must(sonarqubev1alpha1.AddToScheme(scheme)) utilruntime.Must(fortifyv1alpha1.AddToScheme(scheme)) + utilruntime.Must(twistlockv1alpha1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } -- GitLab From 09d90ca71f61dd13aae9403f97bb592a0e9b55cd Mon Sep 17 00:00:00 2001 From: project_646_bot Date: Wed, 16 Jun 2021 16:52:17 -1000 Subject: [PATCH 07/16] fix: reconcile called when status is updated --- ...fortify_v1alpha1_fortifypipelineconfiguration.yaml | 4 ++-- .../fortifypipelineconfiguration_controller.go | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/config/samples/fortify_v1alpha1_fortifypipelineconfiguration.yaml b/config/samples/fortify_v1alpha1_fortifypipelineconfiguration.yaml index 09af997..eafd893 100644 --- a/config/samples/fortify_v1alpha1_fortifypipelineconfiguration.yaml +++ b/config/samples/fortify_v1alpha1_fortifypipelineconfiguration.yaml @@ -3,7 +3,7 @@ kind: FortifyPipelineConfiguration metadata: name: fortifypipelineconfiguration-sample spec: - projectName: "Valkyrie7" - language: "javascript" + projectName: "Valkyrie10" + language: "java" credentialName: fortifycredential-sample diff --git a/controllers/fortify/fortifypipelineconfiguration_controller.go b/controllers/fortify/fortifypipelineconfiguration_controller.go index 53c6e0a..8db3d6b 100644 --- a/controllers/fortify/fortifypipelineconfiguration_controller.go +++ b/controllers/fortify/fortifypipelineconfiguration_controller.go @@ -23,6 +23,7 @@ import ( 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" @@ -111,7 +112,7 @@ func (r *FortifyPipelineConfigurationReconciler) Reconcile(ctx context.Context, if !utils.ContainsString(fortifyConfig.GetFinalizers(), finalizerName) { controllerutil.AddFinalizer(&fortifyConfig, finalizerName) if err = r.Update(ctx, &fortifyConfig); err != nil { - log.Error(err, "error updating finalizer list") + log.Error(err, "error adding finalizer to list") return } } @@ -208,7 +209,8 @@ func (r *FortifyPipelineConfigurationReconciler) Reconcile(ctx context.Context, log.Info("removing project from Fortify: " + fortifyConfig.Spec.ProjectName) existingProjectID := fortifyConfig.Status.ProjectID if existingProjectID != nil { - err = fc.DeleteProjectVersion(*existingProjectID) + //TODO: uncomment for production + //err = fc.DeleteProjectVersion(*existingProjectID) if err != nil { log.Error(err, "error deleting project from Fortify") return @@ -216,11 +218,12 @@ func (r *FortifyPipelineConfigurationReconciler) Reconcile(ctx context.Context, } 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 updating Fortify resource status") + log.Error(err, "error removing finalizer from list") return } } @@ -442,7 +445,9 @@ func createBulkUpdateRequest(serverAPIURL string, versionID string, language str // SetupWithManager sets up the controller with the Manager. func (r *FortifyPipelineConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error { + pred := predicate.GenerationChangedPredicate{} return ctrl.NewControllerManagedBy(mgr). For(&fortifyv1alpha1.FortifyPipelineConfiguration{}). + WithEventFilter(pred). Complete(r) } -- GitLab From 7c3b63af64bd58759f7d6c4a42a09829efe767e3 Mon Sep 17 00:00:00 2001 From: project_646_bot Date: Thu, 17 Jun 2021 09:04:19 -1000 Subject: [PATCH 08/16] fix formating --- apis/gitlab/v1alpha1/group_types_test.go | 4 +- .../customer.valkyrie.dso.mil_customers.yaml | 9 +- ...ab.valkyrie.dso.mil_gitlabcredentials.yaml | 12 +- .../bases/gitlab.valkyrie.dso.mil_groups.yaml | 21 +- .../gitlab.valkyrie.dso.mil_projects.yaml | 2 +- controllers/gitlab/gitlab_client.go | 111 +- controllers/gitlab/group_controller.go | 630 +++-- controllers/gitlab/group_controller_test.go | 2297 ++++++++--------- controllers/gitlab/mocks_test.go | 847 +++--- 9 files changed, 1967 insertions(+), 1966 deletions(-) diff --git a/apis/gitlab/v1alpha1/group_types_test.go b/apis/gitlab/v1alpha1/group_types_test.go index 03871c4..bf3d636 100644 --- a/apis/gitlab/v1alpha1/group_types_test.go +++ b/apis/gitlab/v1alpha1/group_types_test.go @@ -70,13 +70,13 @@ func initVarsGroup() testVarsGroup { testVars.testObjectSpec1 = GroupSpec{ Name: "testGroup1", GitlabCredentialsName: "nameOfTheCredentials", - Description: "testDescription1", + Description: "testDescription1", } testVars.testObjectSpec2 = GroupSpec{ Name: "testGroup2", GitlabCredentialsName: "nameOfCredentials2", ProjectSpecs: nil, - Description: "testDescription2", + Description: "testDescription2", } id1 := int64(1) diff --git a/config/crd/bases/customer.valkyrie.dso.mil_customers.yaml b/config/crd/bases/customer.valkyrie.dso.mil_customers.yaml index 943d3ba..270ceee 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/gitlab.valkyrie.dso.mil_gitlabcredentials.yaml b/config/crd/bases/gitlab.valkyrie.dso.mil_gitlabcredentials.yaml index 1c6de61..5cb1023 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 ccbce45..4138a6b 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 7db3475..8f9eced 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: ServerURL 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/controllers/gitlab/gitlab_client.go b/controllers/gitlab/gitlab_client.go index abc9052..57999a4 100644 --- a/controllers/gitlab/gitlab_client.go +++ b/controllers/gitlab/gitlab_client.go @@ -1,56 +1,55 @@ -package gitlab - -import gitlab "github.com/xanzy/go-gitlab" - -// Client is a facade for go-gitlab -type Client interface { - NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) - ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) - ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) - CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) - UpdateGroup(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) - -} - -// ClientImpl is the Default implementation for the facade. -type ClientImpl struct { - client *gitlab.Client -} - -// NewClient is a facade for the go-gitlab client NewClient call -func (c ClientImpl) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - client, err := gitlab.NewClient(token, options...) - c.client = client - return client, err -} - -// ListGroups is a facade for the go-gitlab client.Groups.ListGroups call. -func (c ClientImpl) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { - return c.client.Groups.ListGroups(opt, options...) -} - -// ListGroupsByName is a facade for the go-gitlab client.Groups.ListGroups call, with the group name as a search option. -func (c ClientImpl) ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return c.client.Groups.ListGroups( - &gitlab.ListGroupsOptions{ - Search: &name, - }) -} - -// CreateGroup is a facade for the go-gitlab client.Groups.CreateGroup -func (c ClientImpl) CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) { - opt := gitlab.CreateGroupOptions{ - Name: &name, - Description: &description, - } - return c.client.Groups.CreateGroup(&opt) -} - -// UpdateGroup is a facade for the go-gitlab client.Group.UpdateGroup -func (c ClientImpl) UpdateGroup(id string, name string, descrption string) (*gitlab.Group, *gitlab.Response, error) { - opt := gitlab.UpdateGroupOptions{ - Name: &name, - Description: &descrption, - } - return c.client.Groups.UpdateGroup(id, &opt) -} +package gitlab + +import gitlab "github.com/xanzy/go-gitlab" + +// Client is a facade for go-gitlab +type Client interface { + NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) + ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) + ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) + CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) + UpdateGroup(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) +} + +// ClientImpl is the Default implementation for the facade. +type ClientImpl struct { + client *gitlab.Client +} + +// NewClient is a facade for the go-gitlab client NewClient call +func (c ClientImpl) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + client, err := gitlab.NewClient(token, options...) + c.client = client + return client, err +} + +// ListGroups is a facade for the go-gitlab client.Groups.ListGroups call. +func (c ClientImpl) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { + return c.client.Groups.ListGroups(opt, options...) +} + +// ListGroupsByName is a facade for the go-gitlab client.Groups.ListGroups call, with the group name as a search option. +func (c ClientImpl) ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return c.client.Groups.ListGroups( + &gitlab.ListGroupsOptions{ + Search: &name, + }) +} + +// CreateGroup is a facade for the go-gitlab client.Groups.CreateGroup +func (c ClientImpl) CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) { + opt := gitlab.CreateGroupOptions{ + Name: &name, + Description: &description, + } + return c.client.Groups.CreateGroup(&opt) +} + +// UpdateGroup is a facade for the go-gitlab client.Group.UpdateGroup +func (c ClientImpl) UpdateGroup(id string, name string, descrption string) (*gitlab.Group, *gitlab.Response, error) { + opt := gitlab.UpdateGroupOptions{ + Name: &name, + Description: &descrption, + } + return c.client.Groups.UpdateGroup(id, &opt) +} diff --git a/controllers/gitlab/group_controller.go b/controllers/gitlab/group_controller.go index db8c208..46f433d 100644 --- a/controllers/gitlab/group_controller.go +++ b/controllers/gitlab/group_controller.go @@ -1,316 +1,314 @@ -/* -Copyright 2021. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package gitlab - -import ( - "context" - "fmt" - "github.com/go-logr/logr" - "github.com/xanzy/go-gitlab" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "time" - gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" -) - -// Errors -const ( - errorUnableToFetchGroup = "unable to fetch group" - errorUnableToFetchGitlabCredentials = "unable to fetch gitlab credentials" - errorUnableToFetchSecret = "unable to fetch secret from gitlab credentials" - errorUnableToCreateGitlabClient = "unable to create gitlab client" - errorWhileSearchingGroups = "Error while searching groups." - errorWhileCreatingGitlabGroup = "Error while creating GitLab group." - errorWhileUpdatingGitlabGroup = "Error while updating GitLab group." - errorUnableToUpdateStatus = "Unable to update status." - errorUnableToProcessProjects = "Unable to process projects" - errorTryingToCreateProject = "Error trying to create project" - errorLookingUpProject = "Error looking up project" - errorUpdatingProject = "Error updating project" -) - -// Group Status -const ( - CredentialNotFound = "CredentialNotFound" - CredentialSecretNotFound = "CredentialSecretNotFound" - GroupCreated = "Created" - GroupOK = "OK" -) - -const valkyrie = "valkyrie" - - -// GroupReconciler reconciles the state of a Gitlab Group, for each reconciliation loop it will log in to Gitlab -// with credentials in the secret provided. It will then check the state of the Group, and create it if necessary. -type GroupReconciler struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - gitlabClient Client -} - -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups/finalizers,verbs=update -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=gitlabcredentials,verbs=get;list;watch -//+kubebuilder:rbac:groups=core,resources=secretreference,verbs=get;list;watch -//+kubebuilder:rbac:groups=core,resources=secret,verbs=get;list;watch -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=projects,verbs=get;list;watch;create;update;patch;delete - -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// The reconcile loop for the Gitlab Group will update the Group and any of the child Project API Objects. -func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := r.Log.WithValues("group", req.NamespacedName) - - group := &gitlabv1alpha1.Group{} - - // Get the Group Object - if err := r.Get(ctx, req.NamespacedName, group); err != nil { - log.Error(err, errorUnableToFetchGroup, "group", req.NamespacedName.Name) - // we'll ignore not-found errors, since they can't be fixed by an immediate - // requeue (we'll need to wait for a new notification), and we can get them - // on deleted requests. - return ctrl.Result{}, client.IgnoreNotFound(err) - } - - // Get the Gitlab Credentials - var gitLabCredentialsName = types.NamespacedName{ - Namespace: req.Namespace, - Name: group.Spec.GitlabCredentialsName, - } - - var gitlabCredentials gitlabv1alpha1.GitlabCredentials - if err := r.Get(ctx, gitLabCredentialsName, &gitlabCredentials); err != nil { - - log.Error(err, errorUnableToFetchGitlabCredentials) - group.Status.State = CredentialNotFound - _ = r.updateStatus(ctx, 0, group) - return ctrl.Result{Requeue: true}, err - } - - var secretName = types.NamespacedName{ - Namespace: gitlabCredentials.Spec.AccessToken.Namespace, - Name: gitlabCredentials.Spec.AccessToken.Name, - } - - var secret v1.Secret - // Get the Secret - if err := r.Get(ctx, secretName, &secret); err != nil { - - log.Error(err, errorUnableToFetchSecret) - group.Status.State = CredentialSecretNotFound - _ = r.updateStatus(ctx, 0, group) - - return ctrl.Result{Requeue: true}, err - } - - // Login to Gitlab - var accessToken = string(secret.Data[gitlabCredentials.Spec.AccessTokenKey]) - - var err error - if _, err = r.gitlabClient.NewClient(accessToken, gitlab.WithBaseURL(gitlabCredentials.Spec.URL)); err != nil { - log.Error(err, errorUnableToCreateGitlabClient, "username", gitlabCredentials.Spec.Username, "url", gitlabCredentials.Spec.URL) - return ctrl.Result{Requeue: true}, err - } - - // See if the Group Exists - var gitlabGroup *gitlab.Group - found, _, gitlabGroup, err := r.groupExists(group) - if err != nil { - log.Error(err, errorWhileSearchingGroups, "status") - return ctrl.Result{Requeue: true}, err - } - - if !found { - if gitlabGroup, _, err = r.createGroup(group); err != nil { - - log.Error(err, errorWhileCreatingGitlabGroup, "group", group) - return ctrl.Result{Requeue: true}, err - } - } else { - if gitlabGroup, _, err = r.updateGroup(group); err != nil { - log.Error(err, errorWhileUpdatingGitlabGroup, "group", group) - return ctrl.Result{Requeue: true}, err - } - } - - if err := r.updateStatus(ctx, gitlabGroup.ID, group); err != nil { - log.Error(err, errorUnableToUpdateStatus, "group", group) - return ctrl.Result{ Requeue: true }, err - } - - if err := r.processProjects(ctx, group, group.Spec.ProjectSpecs); err != nil { - log.Error(err, errorUnableToProcessProjects, "group", group) - return ctrl.Result{Requeue: true}, err - } - - return ctrl.Result{}, nil -} - -func (r *GroupReconciler) groupExists(group *gitlabv1alpha1.Group) (bool, *gitlab.Response, *gitlab.Group, error) { - - var grouplist []*gitlab.Group - var response *gitlab.Response - var err error - if grouplist, response, err = r.gitlabClient.ListGroupsByName(group.Spec.Name); err != nil { - return false, response, nil, err - } - - // TODO: (jvb) For work beyond the MVP we'll need to handle pagination. May be superceeded by adam's client work. - found := false - for _, groupInList := range grouplist { - found = group.Spec.Name == groupInList.Name - if found { - return found, response, groupInList, nil - } - } - return found, response, nil, nil -} - -func (r *GroupReconciler) createGroup(group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { - gitlabGroup, response, err := r.gitlabClient.CreateGroup(group.Spec.Name, group.Spec.Description) - - if err == nil { - group.Status.CreatedTime = metav1.Time{ - Time: time.Now(), - } - group.ObjectMeta.Annotations["ID"] = fmt.Sprint(gitlabGroup.ID) - group.ObjectMeta.Annotations["Creator"] = valkyrie - group.Status.State = GroupCreated - } - - return gitlabGroup, response, err -} - -func (r *GroupReconciler) updateGroup(group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { - gitlabGroup, response, err := r.gitlabClient.UpdateGroup(group.ObjectMeta.Annotations["ID"], group.Spec.Name, group.Spec.Description) - - if err == nil { - group.Status.State = GroupOK - group.Status.LastUpdatedTime = metav1.Time{ - Time: time.Now(), - } - } - - return gitlabGroup, response, err -} - - -func (r *GroupReconciler) processProjects(ctx context.Context, group *gitlabv1alpha1.Group, specs []gitlabv1alpha1.ProjectSpec) error { - for _, projectSpec := range specs { - if project, foundProject, err := r.getProject(ctx, group, projectSpec); err != nil { - r.Log.Error(err, errorLookingUpProject, "name", projectSpec.Name) - return err - } else if foundProject { - if _, err := r.updateProject(ctx, projectSpec, project); err != nil { - - r.Log.Error(err, errorUpdatingProject, "project", project) - } - } else { - if err := r.createProject(ctx, group, projectSpec); err != nil { - - r.Log.Error(err, errorTryingToCreateProject, "project", projectSpec) - return err - } - } - } - return nil -} - -func (r *GroupReconciler) getProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec) (*gitlabv1alpha1.Project, bool, error) { - var project gitlabv1alpha1.Project - projectName := types.NamespacedName{ - Namespace: group.Namespace, - Name: fmt.Sprintf("%s-%s", group.Name, spec.Name), - } - err := r.Get(ctx, projectName, &project) - - projectNotFound := client.IgnoreNotFound(err) - - if projectNotFound == nil { - return nil, false, nil - } - - return &project, true, err -} - -func (r *GroupReconciler) createProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec) error { - blockOwnerDeletion := true - managingController := true - return r.Create(ctx, &gitlabv1alpha1.Project{ - ObjectMeta: ctrl.ObjectMeta{ - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: group.APIVersion, - Kind: group.Kind, - Name: group.Name, - UID: group.UID, - Controller: &managingController, - BlockOwnerDeletion: &blockOwnerDeletion, - }, - }, - }, - Spec: gitlabv1alpha1.ProjectSpec{ - Name: spec.Name, - Path: spec.Path, - ImpactLevel: spec.ImpactLevel, - StorageTypes: spec.StorageTypes, - VirtualService: spec.VirtualService, - ServicePort: spec.ServicePort, - Language: spec.Language, - }, - }) -} - -func (r *GroupReconciler) updateProject(ctx context.Context, spec gitlabv1alpha1.ProjectSpec, project *gitlabv1alpha1.Project) (*gitlabv1alpha1.Project, error) { - project.Spec.Name = spec.Name - project.Spec.Path = spec.Path - project.Spec.ImpactLevel = spec.ImpactLevel - project.Spec.StorageTypes = spec.StorageTypes - project.Spec.VirtualService = spec.VirtualService - project.Spec.ServicePort = spec.ServicePort - project.Spec.Language = spec.Language - return project, r.Update(ctx, project) -} - -func (r *GroupReconciler) updateStatus(ctx context.Context, id int, group *gitlabv1alpha1.Group) error { - if id != 0 { - id64 := int64(id) - group.Status.GitlabID = &id64 - group.Status.LastUpdatedTime = metav1.Time{ - Time: time.Now(), - } - } - - return r.Status().Update(ctx, group) -} - -// SetupWithManager sets up the controller with the Manager. -func (r *GroupReconciler) SetupWithManager(mgr ctrl.Manager) error { - // Setup a default GitlabClient implementation - r.gitlabClient = &ClientImpl{} - - return ctrl.NewControllerManagedBy(mgr). - For(&gitlabv1alpha1.Group{}). - Owns(&gitlabv1alpha1.Project{}). - Complete(r) -} \ No newline at end of file +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gitlab + +import ( + "context" + "fmt" + "github.com/go-logr/logr" + "github.com/xanzy/go-gitlab" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "time" + gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" +) + +// Errors +const ( + errorUnableToFetchGroup = "unable to fetch group" + errorUnableToFetchGitlabCredentials = "unable to fetch gitlab credentials" + errorUnableToFetchSecret = "unable to fetch secret from gitlab credentials" + errorUnableToCreateGitlabClient = "unable to create gitlab client" + errorWhileSearchingGroups = "Error while searching groups." + errorWhileCreatingGitlabGroup = "Error while creating GitLab group." + errorWhileUpdatingGitlabGroup = "Error while updating GitLab group." + errorUnableToUpdateStatus = "Unable to update status." + errorUnableToProcessProjects = "Unable to process projects" + errorTryingToCreateProject = "Error trying to create project" + errorLookingUpProject = "Error looking up project" + errorUpdatingProject = "Error updating project" +) + +// Group Status +const ( + CredentialNotFound = "CredentialNotFound" + CredentialSecretNotFound = "CredentialSecretNotFound" + GroupCreated = "Created" + GroupOK = "OK" +) + +const valkyrie = "valkyrie" + +// GroupReconciler reconciles the state of a Gitlab Group, for each reconciliation loop it will log in to Gitlab +// with credentials in the secret provided. It will then check the state of the Group, and create it if necessary. +type GroupReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + gitlabClient Client +} + +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups/finalizers,verbs=update +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=gitlabcredentials,verbs=get;list;watch +//+kubebuilder:rbac:groups=core,resources=secretreference,verbs=get;list;watch +//+kubebuilder:rbac:groups=core,resources=secret,verbs=get;list;watch +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=projects,verbs=get;list;watch;create;update;patch;delete + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// The reconcile loop for the Gitlab Group will update the Group and any of the child Project API Objects. +func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := r.Log.WithValues("group", req.NamespacedName) + + group := &gitlabv1alpha1.Group{} + + // Get the Group Object + if err := r.Get(ctx, req.NamespacedName, group); err != nil { + log.Error(err, errorUnableToFetchGroup, "group", req.NamespacedName.Name) + // we'll ignore not-found errors, since they can't be fixed by an immediate + // requeue (we'll need to wait for a new notification), and we can get them + // on deleted requests. + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + // Get the Gitlab Credentials + var gitLabCredentialsName = types.NamespacedName{ + Namespace: req.Namespace, + Name: group.Spec.GitlabCredentialsName, + } + + var gitlabCredentials gitlabv1alpha1.GitlabCredentials + if err := r.Get(ctx, gitLabCredentialsName, &gitlabCredentials); err != nil { + + log.Error(err, errorUnableToFetchGitlabCredentials) + group.Status.State = CredentialNotFound + _ = r.updateStatus(ctx, 0, group) + return ctrl.Result{Requeue: true}, err + } + + var secretName = types.NamespacedName{ + Namespace: gitlabCredentials.Spec.AccessToken.Namespace, + Name: gitlabCredentials.Spec.AccessToken.Name, + } + + var secret v1.Secret + // Get the Secret + if err := r.Get(ctx, secretName, &secret); err != nil { + + log.Error(err, errorUnableToFetchSecret) + group.Status.State = CredentialSecretNotFound + _ = r.updateStatus(ctx, 0, group) + + return ctrl.Result{Requeue: true}, err + } + + // Login to Gitlab + var accessToken = string(secret.Data[gitlabCredentials.Spec.AccessTokenKey]) + + var err error + if _, err = r.gitlabClient.NewClient(accessToken, gitlab.WithBaseURL(gitlabCredentials.Spec.URL)); err != nil { + log.Error(err, errorUnableToCreateGitlabClient, "username", gitlabCredentials.Spec.Username, "url", gitlabCredentials.Spec.URL) + return ctrl.Result{Requeue: true}, err + } + + // See if the Group Exists + var gitlabGroup *gitlab.Group + found, _, gitlabGroup, err := r.groupExists(group) + if err != nil { + log.Error(err, errorWhileSearchingGroups, "status") + return ctrl.Result{Requeue: true}, err + } + + if !found { + if gitlabGroup, _, err = r.createGroup(group); err != nil { + + log.Error(err, errorWhileCreatingGitlabGroup, "group", group) + return ctrl.Result{Requeue: true}, err + } + } else { + if gitlabGroup, _, err = r.updateGroup(group); err != nil { + log.Error(err, errorWhileUpdatingGitlabGroup, "group", group) + return ctrl.Result{Requeue: true}, err + } + } + + if err := r.updateStatus(ctx, gitlabGroup.ID, group); err != nil { + log.Error(err, errorUnableToUpdateStatus, "group", group) + return ctrl.Result{Requeue: true}, err + } + + if err := r.processProjects(ctx, group, group.Spec.ProjectSpecs); err != nil { + log.Error(err, errorUnableToProcessProjects, "group", group) + return ctrl.Result{Requeue: true}, err + } + + return ctrl.Result{}, nil +} + +func (r *GroupReconciler) groupExists(group *gitlabv1alpha1.Group) (bool, *gitlab.Response, *gitlab.Group, error) { + + var grouplist []*gitlab.Group + var response *gitlab.Response + var err error + if grouplist, response, err = r.gitlabClient.ListGroupsByName(group.Spec.Name); err != nil { + return false, response, nil, err + } + + // TODO: (jvb) For work beyond the MVP we'll need to handle pagination. May be superceeded by adam's client work. + found := false + for _, groupInList := range grouplist { + found = group.Spec.Name == groupInList.Name + if found { + return found, response, groupInList, nil + } + } + return found, response, nil, nil +} + +func (r *GroupReconciler) createGroup(group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { + gitlabGroup, response, err := r.gitlabClient.CreateGroup(group.Spec.Name, group.Spec.Description) + + if err == nil { + group.Status.CreatedTime = metav1.Time{ + Time: time.Now(), + } + group.ObjectMeta.Annotations["ID"] = fmt.Sprint(gitlabGroup.ID) + group.ObjectMeta.Annotations["Creator"] = valkyrie + group.Status.State = GroupCreated + } + + return gitlabGroup, response, err +} + +func (r *GroupReconciler) updateGroup(group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { + gitlabGroup, response, err := r.gitlabClient.UpdateGroup(group.ObjectMeta.Annotations["ID"], group.Spec.Name, group.Spec.Description) + + if err == nil { + group.Status.State = GroupOK + group.Status.LastUpdatedTime = metav1.Time{ + Time: time.Now(), + } + } + + return gitlabGroup, response, err +} + +func (r *GroupReconciler) processProjects(ctx context.Context, group *gitlabv1alpha1.Group, specs []gitlabv1alpha1.ProjectSpec) error { + for _, projectSpec := range specs { + if project, foundProject, err := r.getProject(ctx, group, projectSpec); err != nil { + r.Log.Error(err, errorLookingUpProject, "name", projectSpec.Name) + return err + } else if foundProject { + if _, err := r.updateProject(ctx, projectSpec, project); err != nil { + + r.Log.Error(err, errorUpdatingProject, "project", project) + } + } else { + if err := r.createProject(ctx, group, projectSpec); err != nil { + + r.Log.Error(err, errorTryingToCreateProject, "project", projectSpec) + return err + } + } + } + return nil +} + +func (r *GroupReconciler) getProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec) (*gitlabv1alpha1.Project, bool, error) { + var project gitlabv1alpha1.Project + projectName := types.NamespacedName{ + Namespace: group.Namespace, + Name: fmt.Sprintf("%s-%s", group.Name, spec.Name), + } + err := r.Get(ctx, projectName, &project) + + projectNotFound := client.IgnoreNotFound(err) + + if projectNotFound == nil { + return nil, false, nil + } + + return &project, true, err +} + +func (r *GroupReconciler) createProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec) error { + blockOwnerDeletion := true + managingController := true + return r.Create(ctx, &gitlabv1alpha1.Project{ + ObjectMeta: ctrl.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: group.APIVersion, + Kind: group.Kind, + Name: group.Name, + UID: group.UID, + Controller: &managingController, + BlockOwnerDeletion: &blockOwnerDeletion, + }, + }, + }, + Spec: gitlabv1alpha1.ProjectSpec{ + Name: spec.Name, + Path: spec.Path, + ImpactLevel: spec.ImpactLevel, + StorageTypes: spec.StorageTypes, + VirtualService: spec.VirtualService, + ServicePort: spec.ServicePort, + Language: spec.Language, + }, + }) +} + +func (r *GroupReconciler) updateProject(ctx context.Context, spec gitlabv1alpha1.ProjectSpec, project *gitlabv1alpha1.Project) (*gitlabv1alpha1.Project, error) { + project.Spec.Name = spec.Name + project.Spec.Path = spec.Path + project.Spec.ImpactLevel = spec.ImpactLevel + project.Spec.StorageTypes = spec.StorageTypes + project.Spec.VirtualService = spec.VirtualService + project.Spec.ServicePort = spec.ServicePort + project.Spec.Language = spec.Language + return project, r.Update(ctx, project) +} + +func (r *GroupReconciler) updateStatus(ctx context.Context, id int, group *gitlabv1alpha1.Group) error { + if id != 0 { + id64 := int64(id) + group.Status.GitlabID = &id64 + group.Status.LastUpdatedTime = metav1.Time{ + Time: time.Now(), + } + } + + return r.Status().Update(ctx, group) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *GroupReconciler) SetupWithManager(mgr ctrl.Manager) error { + // Setup a default GitlabClient implementation + r.gitlabClient = &ClientImpl{} + + return ctrl.NewControllerManagedBy(mgr). + For(&gitlabv1alpha1.Group{}). + Owns(&gitlabv1alpha1.Project{}). + Complete(r) +} diff --git a/controllers/gitlab/group_controller_test.go b/controllers/gitlab/group_controller_test.go index 3dd2024..c97e086 100755 --- a/controllers/gitlab/group_controller_test.go +++ b/controllers/gitlab/group_controller_test.go @@ -1,1156 +1,1141 @@ -package gitlab - -import ( - "context" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/xanzy/go-gitlab" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "net/http" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" -) - - -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("it looks up the API Object", func() { - Context("the API Object isn't found", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch group")) - }) - It("should return an empty result, and ignore the error.", func() { - Expect(result).Should(Equal(ctrl.Result{})) - Expect(err).Should(BeNil()) - }) - }) - }) -}) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("it looks up the GitlabCredentals", func() { - Context("gitlab credentials are not found", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - clientMock.GetFunction = nil - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{}, - Status: gitlabv1alpha1.GroupStatus{}, - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - result, err := sut.Reconcile(contextMock, requestMock) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch gitlab credentials")) - }) - It("should return a reconcile result, and the error.", func() { - Expect(result).To(Equal(ctrl.Result{ - Requeue: true, - })) - Expect(err).ToNot(BeNil()) - }) - }) - }) - }) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("getting the secret from the GitlabCredentials", func() { - Context("Secret doesn't exist", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - result, err := sut.Reconcile(contextMock, requestMock) - It("Should log an error regarding the missing credentials", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch secret from gitlab credentials")) - }) - It("Should return a requeue result and an error", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - Expect(err).ToNot(BeNil()) - }) - }) - }) - }) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("logging in to GitLab", func() { - Context("can't create client", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return nil, &MockError{ - message: "mocked error", - } - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("Should log the failure", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorUnableToCreateGitlabClient)) - }) - It("Should return an error", func() { - Expect(err).ToNot(BeNil()) - }) - It("should requeue the group", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - }) - }) - }) - -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("groupExists fails", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, &MockError{ - message: "Error", - } - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorWhileSearchingGroups)) - }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) - }) - }) - }) - -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("createGroup fails", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - groups := []*gitlab.Group{ &gitlab.Group{ - ID: 0, - Name: "", - Path: "", - Description: "", - MembershipLock: false, - Visibility: "", - LFSEnabled: false, - AvatarURL: "", - WebURL: "", - RequestAccessEnabled: false, - FullName: "", - FullPath: "", - ParentID: 0, - Projects: nil, - Statistics: nil, - CustomAttributes: nil, - ShareWithGroupLock: false, - RequireTwoFactorAuth: false, - TwoFactorGracePeriod: 0, - ProjectCreationLevel: "", - AutoDevopsEnabled: false, - SubGroupCreationLevel: "", - EmailsDisabled: false, - MentionsDisabled: false, - RunnersToken: "", - SharedProjects: nil, - SharedWithGroups: nil, - LDAPCN: "", - LDAPAccess: 0, - LDAPGroupLinks: nil, - SharedRunnersMinutesLimit: 0, - ExtraSharedRunnersMinutesLimit: 0, - MarkedForDeletionOn: nil, - CreatedAt: nil, - } } - return groups, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, nil - }, - createGroupFunction: func(name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{}, &MockError{ - message: "Error", - } - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorWhileCreatingGitlabGroup)) - }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) - }) - }) - }) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("updateGroup fails", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - groups := []*gitlab.Group{ {Name: "agroup"} } - return groups, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, nil - }, - updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{}, &MockError{ - message: "Error", - } - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorWhileUpdatingGitlabGroup)) - }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) - }) - }) - }) - -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("updateStatus fails", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ - statusWriter: &MockStatusWriter{ - updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return &MockError{ "error"} - }, - }, - } - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - groups := []*gitlab.Group{ {Name: "agroup"} } - return groups, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, nil - }, - updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return &gitlab.Group{ Name: "agroup" }, &gitlab.Response{}, nil - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorUnableToUpdateStatus)) - }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) - }) - }) - }) - -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("processProjects fails", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ - statusWriter: &MockStatusWriter{ - updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }, - }, - createFunction: func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error{ - return &MockError{message: "error"} - }, - } - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: []gitlabv1alpha1.ProjectSpec{ { Name: "ProjectSpec"} }, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - groups := []*gitlab.Group{ {Name: "agroup"} } - return groups, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, nil - }, - updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return &gitlab.Group{ Name: "agroup" }, &gitlab.Response{}, nil - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorUnableToProcessProjects)) - }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) - }) - }) - }) - -var _ = - Describe("groupExists", func() { - When("the gitlab client fails", func() { - sut := GroupReconciler{ - gitlabClient: &MockGitlabClient{ - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{}, &MockError{ - message: "mockedError", - } - }, - }, - } - found, response, groups, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "An error"}}) - It("should return that it didn't find the group", func() { - Expect(found).To(Equal(false)) - }) - It("should return the gitlab response", func() { - Expect(response).ToNot(BeNil()) - }) - It("should return a nil group", func() { - Expect(groups).To(BeNil()) - }) - It("should return an error", func() { - Expect(err).ToNot(BeNil()) - }) - }) - When("the group is in the list", func() { - group := &gitlab.Group{ Name: "A group" } - returnGroups := []*gitlab.Group{ group } - sut := GroupReconciler{ - gitlabClient: &MockGitlabClient{ - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return returnGroups, &gitlab.Response{}, nil - }, - }, - } - - found, response, returnedGroup, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "A group"}}) - It("should return that it found the group", func() { - Expect(found).To(Equal(true)) - }) - It("should return the gitlab response", func() { - Expect(response).ToNot(BeNil()) - }) - It("should return list with the group", func() { - Expect(returnedGroup).To(Equal(group)) - }) - It("should not return an error", func() { - Expect(err).To(BeNil()) - }) - }) - When("the group is not in the list", func() { - sut := GroupReconciler{ - gitlabClient: &MockGitlabClient{ - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{}, nil - }, - }, - } - - found, response, groups, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "A group"}}) - It("should return that it did not find the group", func() { - Expect(found).To(Equal(false)) - }) - It("should return the gitlab response", func() { - Expect(response).ToNot(BeNil()) - }) - It("should return nil for the group list", func() { - Expect(groups).To(BeNil()) - }) - It("should not return an error", func() { - Expect(err).To(BeNil()) - }) - }) - }) -var _ = - Describe("createGroup", func() { - expectedGroup := &gitlab.Group{ ID: 1, Name: "A test group"} - expectedResponse := &gitlab.Response{ - Response: &http.Response{}, - TotalItems: 1, - TotalPages: 1, - ItemsPerPage: 10, - CurrentPage: 1, - NextPage: 0, - PreviousPage: 0, - } - sut := GroupReconciler{ - gitlabClient: &MockGitlabClient{ - createGroupFunction: func(name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return expectedGroup, expectedResponse, nil - }, - }, - } - group, response, err := sut.createGroup( - &gitlabv1alpha1.Group{ - ObjectMeta: ctrl.ObjectMeta{Annotations: make(map[string]string)}, - Spec: gitlabv1alpha1.GroupSpec{Name: "a group"}, - }) - It("should return the gitlab group created", func() { - Expect(group).To(Equal(expectedGroup)) - }) - It("should return the gitlab response", func(){ - Expect(response).To(Equal(expectedResponse)) - }) - It("should return the error created by the call, or nil", func() { - Expect(err).To(BeNil()) - }) - }) - -var _ = - Describe("updateGroup", func() { - expectedGroup := &gitlab.Group{ID: 2, Name: "A test group"} - expectedResponse := &gitlab.Response{ - Response: &http.Response{}, - TotalItems: 1, - TotalPages: 1, - ItemsPerPage: 10, - CurrentPage: 1, - NextPage: 0, - PreviousPage: 0, - } - sut := GroupReconciler{ - gitlabClient: &MockGitlabClient{ - updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return expectedGroup, expectedResponse, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return []*gitlab.Group{{Name: "a group"}}, &gitlab.Response{}, nil - }, - }, - } - group, response, err := sut.updateGroup( - &gitlabv1alpha1.Group{ - ObjectMeta: ctrl.ObjectMeta{Annotations: make(map[string]string)}, - Spec: gitlabv1alpha1.GroupSpec{Name: "a group"}, - Status: gitlabv1alpha1.GroupStatus{ - GitlabID: nil, - CreatedTime: metav1.Time{}, - LastUpdatedTime: metav1.Time{}, - State: "", - }, - }) - It("should return the gitlab group created", func() { - Expect(group).To(Equal(expectedGroup)) - }) - It("should return the gitlab response", func(){ - Expect(response).To(Equal(expectedResponse)) - }) - It("should return the error created by the call, or nil", func() { - Expect(err).To(BeNil()) - }) - }) - -var _ = - Describe("SetupWithManager", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - err := sut.SetupWithManager(&MockManager{ - Log: &loggerMock, - builder: builder, - }) - It("should setup the default gitlab client", func() { - Expect(sut.gitlabClient).To(BeAssignableToTypeOf(&ClientImpl{})) - }) - It("should return no error", func() { - Expect(err).To(BeNil()) - }) - }) - -var _ = Describe("updateStatus", func() { - When("group id is not 0", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - group := gitlabv1alpha1.Group{Status: gitlabv1alpha1.GroupStatus{ - GitlabID: nil, - CreatedTime: metav1.Time{}, - LastUpdatedTime: metav1.Time{}, - State: "", - }} - sut.updateStatus(&MockContext{}, 1, &group) - - It("should update the gitlab id", func() { - Expect(*group.Status.GitlabID).To(Equal(int64(1))) - }) - It("should update the last updated time", func() { - Expect(group.Status.LastUpdatedTime.Time).To(Not(BeNil())) - }) - }) -}) - -var _ = - Describe("updateProject", func() { - spec := gitlabv1alpha1.ProjectSpec{ - Name: "a project", - Path: "https://example.com.path", - ImpactLevel: "2", - StorageTypes: nil, - VirtualService: "default", - ServicePort: 8081, - Language: "golang", - } - project := gitlabv1alpha1.Project{Spec: gitlabv1alpha1.ProjectSpec{}} - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - mockError := &MockError{"true"} - clientMock := MockClient{ - statusWriter: MockStatusWriter{ - updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }, - }, - updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return mockError - }, - } - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - - returnedProject, err := sut.updateProject(context.TODO(), spec, &project) - It("should update the project from the project specification", func() { - Expect(project.Spec.Name).To(Equal(spec.Name)) - Expect(project.Spec.ServicePort).To(Equal(spec.ServicePort)) - Expect(project.Spec.Language).To(Equal(spec.Language)) - Expect(project.Spec.Path).To(Equal(spec.Path)) - Expect(project.Spec.VirtualService).To(Equal(spec.VirtualService)) - Expect(project.Spec.StorageTypes).To(Equal(spec.StorageTypes)) - Expect(project.Spec.ImpactLevel).To(Equal(spec.ImpactLevel)) - }) - It("should call the kubernetes client update function", func() { - Expect(clientMock.updateFunctionCalled).To(BeTrue()) - }) - It("should return the result of the update function", func() { - Expect(err).To(Equal(mockError)) - }) - It("should return the updated project", func() { - Expect(project).To(Equal(*returnedProject)) - }) - }) \ No newline at end of file +package gitlab + +import ( + "context" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/xanzy/go-gitlab" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "net/http" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" +) + +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("it looks up the API Object", func() { + Context("the API Object isn't found", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch group")) + }) + It("should return an empty result, and ignore the error.", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).Should(BeNil()) + }) + }) + }) +}) +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("it looks up the GitlabCredentals", func() { + Context("gitlab credentials are not found", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + clientMock.GetFunction = nil + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{}, + Status: gitlabv1alpha1.GroupStatus{}, + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + result, err := sut.Reconcile(contextMock, requestMock) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch gitlab credentials")) + }) + It("should return a reconcile result, and the error.", func() { + Expect(result).To(Equal(ctrl.Result{ + Requeue: true, + })) + Expect(err).ToNot(BeNil()) + }) + }) + }) +}) +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("getting the secret from the GitlabCredentials", func() { + Context("Secret doesn't exist", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + result, err := sut.Reconcile(contextMock, requestMock) + It("Should log an error regarding the missing credentials", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch secret from gitlab credentials")) + }) + It("Should return a requeue result and an error", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + Expect(err).ToNot(BeNil()) + }) + }) + }) +}) +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("logging in to GitLab", func() { + Context("can't create client", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string{"accessToken": "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return nil, &MockError{ + message: "mocked error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("Should log the failure", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorUnableToCreateGitlabClient)) + }) + It("Should return an error", func() { + Expect(err).ToNot(BeNil()) + }) + It("should requeue the group", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("groupExists fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string{"accessToken": "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, &MockError{ + message: "Error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorWhileSearchingGroups)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("createGroup fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string{"accessToken": "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{&gitlab.Group{ + ID: 0, + Name: "", + Path: "", + Description: "", + MembershipLock: false, + Visibility: "", + LFSEnabled: false, + AvatarURL: "", + WebURL: "", + RequestAccessEnabled: false, + FullName: "", + FullPath: "", + ParentID: 0, + Projects: nil, + Statistics: nil, + CustomAttributes: nil, + ShareWithGroupLock: false, + RequireTwoFactorAuth: false, + TwoFactorGracePeriod: 0, + ProjectCreationLevel: "", + AutoDevopsEnabled: false, + SubGroupCreationLevel: "", + EmailsDisabled: false, + MentionsDisabled: false, + RunnersToken: "", + SharedProjects: nil, + SharedWithGroups: nil, + LDAPCN: "", + LDAPAccess: 0, + LDAPGroupLinks: nil, + SharedRunnersMinutesLimit: 0, + ExtraSharedRunnersMinutesLimit: 0, + MarkedForDeletionOn: nil, + CreatedAt: nil, + }} + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + createGroupFunction: func(name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, &MockError{ + message: "Error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorWhileCreatingGitlabGroup)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) +}) +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("updateGroup fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string{"accessToken": "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{{Name: "agroup"}} + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, &MockError{ + message: "Error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorWhileUpdatingGitlabGroup)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("updateStatus fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ + statusWriter: &MockStatusWriter{ + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return &MockError{"error"} + }, + }, + } + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string{"accessToken": "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{{Name: "agroup"}} + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return &gitlab.Group{Name: "agroup"}, &gitlab.Response{}, nil + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorUnableToUpdateStatus)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("processProjects fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ + statusWriter: &MockStatusWriter{ + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }, + }, + createFunction: func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + return &MockError{message: "error"} + }, + } + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: []gitlabv1alpha1.ProjectSpec{{Name: "ProjectSpec"}}, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string{"accessToken": "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{{Name: "agroup"}} + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return &gitlab.Group{Name: "agroup"}, &gitlab.Response{}, nil + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorUnableToProcessProjects)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) +}) + +var _ = Describe("groupExists", func() { + When("the gitlab client fails", func() { + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, &MockError{ + message: "mockedError", + } + }, + }, + } + found, response, groups, err := sut.groupExists(&gitlabv1alpha1.Group{Spec: gitlabv1alpha1.GroupSpec{Name: "An error"}}) + It("should return that it didn't find the group", func() { + Expect(found).To(Equal(false)) + }) + It("should return the gitlab response", func() { + Expect(response).ToNot(BeNil()) + }) + It("should return a nil group", func() { + Expect(groups).To(BeNil()) + }) + It("should return an error", func() { + Expect(err).ToNot(BeNil()) + }) + }) + When("the group is in the list", func() { + group := &gitlab.Group{Name: "A group"} + returnGroups := []*gitlab.Group{group} + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return returnGroups, &gitlab.Response{}, nil + }, + }, + } + + found, response, returnedGroup, err := sut.groupExists(&gitlabv1alpha1.Group{Spec: gitlabv1alpha1.GroupSpec{Name: "A group"}}) + It("should return that it found the group", func() { + Expect(found).To(Equal(true)) + }) + It("should return the gitlab response", func() { + Expect(response).ToNot(BeNil()) + }) + It("should return list with the group", func() { + Expect(returnedGroup).To(Equal(group)) + }) + It("should not return an error", func() { + Expect(err).To(BeNil()) + }) + }) + When("the group is not in the list", func() { + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, nil + }, + }, + } + + found, response, groups, err := sut.groupExists(&gitlabv1alpha1.Group{Spec: gitlabv1alpha1.GroupSpec{Name: "A group"}}) + It("should return that it did not find the group", func() { + Expect(found).To(Equal(false)) + }) + It("should return the gitlab response", func() { + Expect(response).ToNot(BeNil()) + }) + It("should return nil for the group list", func() { + Expect(groups).To(BeNil()) + }) + It("should not return an error", func() { + Expect(err).To(BeNil()) + }) + }) +}) +var _ = Describe("createGroup", func() { + expectedGroup := &gitlab.Group{ID: 1, Name: "A test group"} + expectedResponse := &gitlab.Response{ + Response: &http.Response{}, + TotalItems: 1, + TotalPages: 1, + ItemsPerPage: 10, + CurrentPage: 1, + NextPage: 0, + PreviousPage: 0, + } + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + createGroupFunction: func(name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return expectedGroup, expectedResponse, nil + }, + }, + } + group, response, err := sut.createGroup( + &gitlabv1alpha1.Group{ + ObjectMeta: ctrl.ObjectMeta{Annotations: make(map[string]string)}, + Spec: gitlabv1alpha1.GroupSpec{Name: "a group"}, + }) + It("should return the gitlab group created", func() { + Expect(group).To(Equal(expectedGroup)) + }) + It("should return the gitlab response", func() { + Expect(response).To(Equal(expectedResponse)) + }) + It("should return the error created by the call, or nil", func() { + Expect(err).To(BeNil()) + }) +}) + +var _ = Describe("updateGroup", func() { + expectedGroup := &gitlab.Group{ID: 2, Name: "A test group"} + expectedResponse := &gitlab.Response{ + Response: &http.Response{}, + TotalItems: 1, + TotalPages: 1, + ItemsPerPage: 10, + CurrentPage: 1, + NextPage: 0, + PreviousPage: 0, + } + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return expectedGroup, expectedResponse, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return []*gitlab.Group{{Name: "a group"}}, &gitlab.Response{}, nil + }, + }, + } + group, response, err := sut.updateGroup( + &gitlabv1alpha1.Group{ + ObjectMeta: ctrl.ObjectMeta{Annotations: make(map[string]string)}, + Spec: gitlabv1alpha1.GroupSpec{Name: "a group"}, + Status: gitlabv1alpha1.GroupStatus{ + GitlabID: nil, + CreatedTime: metav1.Time{}, + LastUpdatedTime: metav1.Time{}, + State: "", + }, + }) + It("should return the gitlab group created", func() { + Expect(group).To(Equal(expectedGroup)) + }) + It("should return the gitlab response", func() { + Expect(response).To(Equal(expectedResponse)) + }) + It("should return the error created by the call, or nil", func() { + Expect(err).To(BeNil()) + }) +}) + +var _ = Describe("SetupWithManager", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + clientMock := MockClient{statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + err := sut.SetupWithManager(&MockManager{ + Log: &loggerMock, + builder: builder, + }) + It("should setup the default gitlab client", func() { + Expect(sut.gitlabClient).To(BeAssignableToTypeOf(&ClientImpl{})) + }) + It("should return no error", func() { + Expect(err).To(BeNil()) + }) +}) + +var _ = Describe("updateStatus", func() { + When("group id is not 0", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + clientMock := MockClient{statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + group := gitlabv1alpha1.Group{Status: gitlabv1alpha1.GroupStatus{ + GitlabID: nil, + CreatedTime: metav1.Time{}, + LastUpdatedTime: metav1.Time{}, + State: "", + }} + sut.updateStatus(&MockContext{}, 1, &group) + + It("should update the gitlab id", func() { + Expect(*group.Status.GitlabID).To(Equal(int64(1))) + }) + It("should update the last updated time", func() { + Expect(group.Status.LastUpdatedTime.Time).To(Not(BeNil())) + }) + }) +}) + +var _ = Describe("updateProject", func() { + spec := gitlabv1alpha1.ProjectSpec{ + Name: "a project", + Path: "https://example.com.path", + ImpactLevel: "2", + StorageTypes: nil, + VirtualService: "default", + ServicePort: 8081, + Language: "golang", + } + project := gitlabv1alpha1.Project{Spec: gitlabv1alpha1.ProjectSpec{}} + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + mockError := &MockError{"true"} + clientMock := MockClient{ + statusWriter: MockStatusWriter{ + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }, + }, + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return mockError + }, + } + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + + returnedProject, err := sut.updateProject(context.TODO(), spec, &project) + It("should update the project from the project specification", func() { + Expect(project.Spec.Name).To(Equal(spec.Name)) + Expect(project.Spec.ServicePort).To(Equal(spec.ServicePort)) + Expect(project.Spec.Language).To(Equal(spec.Language)) + Expect(project.Spec.Path).To(Equal(spec.Path)) + Expect(project.Spec.VirtualService).To(Equal(spec.VirtualService)) + Expect(project.Spec.StorageTypes).To(Equal(spec.StorageTypes)) + Expect(project.Spec.ImpactLevel).To(Equal(spec.ImpactLevel)) + }) + It("should call the kubernetes client update function", func() { + Expect(clientMock.updateFunctionCalled).To(BeTrue()) + }) + It("should return the result of the update function", func() { + Expect(err).To(Equal(mockError)) + }) + It("should return the updated project", func() { + Expect(project).To(Equal(*returnedProject)) + }) +}) diff --git a/controllers/gitlab/mocks_test.go b/controllers/gitlab/mocks_test.go index ecca40c..1716f5d 100755 --- a/controllers/gitlab/mocks_test.go +++ b/controllers/gitlab/mocks_test.go @@ -1,424 +1,423 @@ -package gitlab - -import ( - "context" - "github.com/go-logr/logr" - "github.com/jinzhu/copier" - "github.com/xanzy/go-gitlab" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/record" - "net/http" - "sigs.k8s.io/controller-runtime/pkg/cache" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/healthz" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/scheme" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "time" - "valkyrie.dso.mil/valkyrie-api/controllers" -) - -type MockClient struct { - GetFunction func(ctx context.Context, key client.ObjectKey, obj client.Object) error - GetCalled bool - expectedObjects map[client.ObjectKey]client.Object - NotFoundError error - statusWriter client.StatusWriter - createFunction func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error - updateFunction func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error - updateFunctionCalled bool -} - -func (m *MockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { - m.GetCalled = true - if m.GetFunction != nil { - return m.GetFunction(ctx, key, obj) - } - - if m.expectedObjects == nil { - return nil - } - - if m.expectedObjects[key] == nil { - return &errors.StatusError{ - ErrStatus: metav1.Status{ - TypeMeta: metav1.TypeMeta{}, - ListMeta: metav1.ListMeta{}, - Status: string(metav1.StatusReasonNotFound), - Message: "NotFound", - Reason: metav1.StatusReasonNotFound, - Details: nil, - Code: 0, - }, - } - } - - foundObject := m.expectedObjects[key] - - copier.Copy(obj, foundObject) - - return nil -} - -func (m *MockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { - panic("implement me") -} - -func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { - if m.createFunction == nil { - return nil - } - return m.createFunction(ctx, obj, opts...) -} - -func (m *MockClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { - panic("implement me") -} - -func (m *MockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - m.updateFunctionCalled = true - if m.updateFunction == nil { - return nil - } - return m.updateFunction(ctx, obj, opts...) -} - -func (m *MockClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { - panic("implement me") -} - -func (m *MockClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { - panic("implement me") -} - -type MockStatusWriter struct { - updateFunction func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error -} - -func (m MockStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return m.updateFunction(ctx, obj, opts...) -} - -func (m MockStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { - panic("implement me") -} - -func (m *MockClient) Status() client.StatusWriter { - return m.statusWriter -} - -func (m *MockClient) Scheme() *runtime.Scheme { - panic("implement me") -} - -func (m *MockClient) RESTMapper() meta.RESTMapper { - panic("implement me") -} - -type MockContext struct { -} - -func (m *MockContext) Deadline() (deadline time.Time, ok bool) { - panic("implement me") -} - -func (m *MockContext) Done() <-chan struct{} { - panic("implement me") -} - -func (m *MockContext) Err() error { - panic("implement me") -} - -func (m *MockContext) Value(key interface{}) interface{} { - panic("implement me") -} - -type MockLogger struct { - WithValuesKeysAndValues []interface{} - WithValuesCalled bool - WithNameValue string - WithNameCalled bool - loggedMessage string - keysAndValues []interface{} - logFunction func(msg string, keysAndValues ...interface{}) - logLevelCalled string - level int -} - -func (m *MockLogger) Enabled() bool { - return true -} - -func (m *MockLogger) Info(msg string, keysAndValues ...interface{}) { - m.loggedMessage = msg - m.keysAndValues = keysAndValues - m.logLevelCalled = "Info" - if m.logFunction != nil { - m.logFunction(msg, keysAndValues) - return - } -} - -func (m *MockLogger) Error(err error, msg string, keysAndValues ...interface{}) { - m.loggedMessage = msg - m.keysAndValues = keysAndValues - m.logLevelCalled = "Error" - if m.logFunction != nil { - m.logFunction(msg, keysAndValues) - return - } -} - -func (m *MockLogger) V(level int) logr.Logger { - m.level = level - return m -} - -func (m *MockLogger) WithValues(keysAndValues ...interface{}) logr.Logger { - m.WithValuesCalled = true - for i := 0; i < len(keysAndValues); i++ { - m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues[i]) - } - m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues) - - return m -} - -func (m *MockLogger) WithName(name string) logr.Logger { - m.WithNameCalled = true - m.WithNameValue = name - return m -} - -type MockController struct { - setupWithManagerWasCalled bool -} - -func (m *MockController) SetupWithManager(manager manager.Manager) error { - m.setupWithManagerWasCalled = true - return nil -} - -type MockManager struct { - Log logr.Logger - addHealthzCheckFunction func(name string, check healthz.Checker) error - addHealthzCheckWasCalled bool - addReadyzCheckFunction func(name string, check healthz.Checker) error - addReadyzCheckWasCalled bool - startFunction func(ctx context.Context) error - startWasCalled bool - builder *scheme.Builder - Fields []interface{} - runnable manager.Runnable -} - -func (m *MockManager) Add(runnable manager.Runnable) error { - m.runnable = runnable - - return nil -} - -func (m *MockManager) Elected() <-chan struct{} { - panic("implement me") -} - -func (m *MockManager) SetFields(i interface{}) error { - if m.Fields == nil { - m.Fields = make([]interface{}, 0) - } - m.Fields = append(m.Fields, i) - - return nil -} - -func (m *MockManager) AddMetricsExtraHandler(path string, handler http.Handler) error { - panic("implement me") -} - -func (m *MockManager) AddHealthzCheck(name string, check healthz.Checker) error { - m.addHealthzCheckWasCalled = true - if m.addHealthzCheckFunction == nil { - return nil - } - return m.addHealthzCheckFunction(name, check) -} - -func (m *MockManager) AddReadyzCheck(name string, check healthz.Checker) error { - m.addReadyzCheckWasCalled = true - if m.addReadyzCheckFunction == nil { - return nil - } - - return m.addReadyzCheckFunction(name, check) -} - -func (m *MockManager) Start(ctx context.Context) error { - m.startWasCalled = true - if m.startFunction == nil { - return nil - } - return m.startFunction(ctx) -} - -func (m *MockManager) GetConfig() *rest.Config { - return &rest.Config{} -} - -func (m *MockManager) GetScheme() *runtime.Scheme { - scheme, _ := m.builder.Build() - return scheme -} - -func (m *MockManager) GetClient() client.Client { - return nil -} - -func (m *MockManager) GetFieldIndexer() client.FieldIndexer { - panic("implement me") -} - -func (m *MockManager) GetCache() cache.Cache { - panic("implement me") -} - -func (m *MockManager) GetEventRecorderFor(name string) record.EventRecorder { - panic("implement me") -} - -func (m *MockManager) GetRESTMapper() meta.RESTMapper { - panic("implement me") -} - -func (m *MockManager) GetAPIReader() client.Reader { - panic("implement me") -} - -func (m *MockManager) GetWebhookServer() *webhook.Server { - panic("implement me") -} - -func (m *MockManager) GetLogger() logr.Logger { - return m.Log -} - -type MockRunFunctionParameters struct { - options zap.Options - metricsAddress string - healthProbeAddress string - enableLeaderElection bool -} - -type MockDriver struct { - parseCommandLineCalled bool - parseCommandLineFunction func() (string, bool, string, zap.Options, error) - runCalled bool - runFunction func(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) - runFunctionParameters MockRunFunctionParameters - newManagerCalled bool - newManagerFunction func(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) - instantiateControllersCalled bool - instantiateControllersFunction func(mgr manager.Manager) []controllers.ManagedController - setupControllerWithManagerCalled bool - setupControllersFunction func(controller controllers.ManagedController, manager manager.Manager) error -} - -func (m *MockDriver) parseCommandLine() (string, bool, string, zap.Options, error) { - m.parseCommandLineCalled = true - if m.parseCommandLineFunction == nil { - return "", true, "", zap.Options{}, nil - } - - return m.parseCommandLineFunction() -} - -func (m *MockDriver) run(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) { - m.runCalled = true - m.runFunctionParameters = MockRunFunctionParameters{ - options: opts, - metricsAddress: metricsAddress, - healthProbeAddress: healthProbeAddress, - enableLeaderElection: enableLeaderElection, - } - if m.runFunction == nil { - return 0, nil - } - - return m.runFunction(opts, metricsAddress, healthProbeAddress, enableLeaderElection) -} - -func (m *MockDriver) newManager(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) { - m.newManagerCalled = true - if m.newManagerFunction == nil { - return &MockManager{}, nil - } - - return m.newManagerFunction(metricsAddress, healthProbeAddress, enableLeaderElection) -} - -func (m *MockDriver) instantiateControllers(mgr manager.Manager) []controllers.ManagedController { - m.instantiateControllersCalled = true - - if m.instantiateControllersFunction == nil { - return []controllers.ManagedController{ - &MockController{}, - } - } - - return m.instantiateControllersFunction(mgr) -} - -func (m *MockDriver) setupControllerWithManager(controller controllers.ManagedController, manager manager.Manager) error { - m.setupControllerWithManagerCalled = true - - if m.setupControllersFunction == nil { - return nil - } - - return m.setupControllersFunction(controller, manager) -} - -type MockError struct { - message string -} - -func (m *MockError) Error() string { - if m.message == "" { - m.message = "mock Error" - } - return m.message -} - -type MockGitlabClient struct { - newClientFunction func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) - listGroupsFunction func(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) - listGroupsByNameFunction func(name string) ([]*gitlab.Group, *gitlab.Response, error) - createGroupFunction func(name string, description string) (*gitlab.Group, *gitlab.Response, error) - updateGroupFunction func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) - -} - -func (m *MockGitlabClient) CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return m.createGroupFunction(name, description) -} - -func (m *MockGitlabClient) UpdateGroup(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return m.updateGroupFunction(id, name, description) -} - -func (m *MockGitlabClient) ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return m.listGroupsByNameFunction(name) -} - -func (m *MockGitlabClient) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return m.newClientFunction(token, options...) -} - -func (m *MockGitlabClient) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { - return m.listGroupsFunction(opt, options...) -} +package gitlab + +import ( + "context" + "github.com/go-logr/logr" + "github.com/jinzhu/copier" + "github.com/xanzy/go-gitlab" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + "net/http" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/scheme" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "time" + "valkyrie.dso.mil/valkyrie-api/controllers" +) + +type MockClient struct { + GetFunction func(ctx context.Context, key client.ObjectKey, obj client.Object) error + GetCalled bool + expectedObjects map[client.ObjectKey]client.Object + NotFoundError error + statusWriter client.StatusWriter + createFunction func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error + updateFunction func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error + updateFunctionCalled bool +} + +func (m *MockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { + m.GetCalled = true + if m.GetFunction != nil { + return m.GetFunction(ctx, key, obj) + } + + if m.expectedObjects == nil { + return nil + } + + if m.expectedObjects[key] == nil { + return &errors.StatusError{ + ErrStatus: metav1.Status{ + TypeMeta: metav1.TypeMeta{}, + ListMeta: metav1.ListMeta{}, + Status: string(metav1.StatusReasonNotFound), + Message: "NotFound", + Reason: metav1.StatusReasonNotFound, + Details: nil, + Code: 0, + }, + } + } + + foundObject := m.expectedObjects[key] + + copier.Copy(obj, foundObject) + + return nil +} + +func (m *MockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + panic("implement me") +} + +func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + if m.createFunction == nil { + return nil + } + return m.createFunction(ctx, obj, opts...) +} + +func (m *MockClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { + panic("implement me") +} + +func (m *MockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + m.updateFunctionCalled = true + if m.updateFunction == nil { + return nil + } + return m.updateFunction(ctx, obj, opts...) +} + +func (m *MockClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + panic("implement me") +} + +func (m *MockClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { + panic("implement me") +} + +type MockStatusWriter struct { + updateFunction func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error +} + +func (m MockStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return m.updateFunction(ctx, obj, opts...) +} + +func (m MockStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + panic("implement me") +} + +func (m *MockClient) Status() client.StatusWriter { + return m.statusWriter +} + +func (m *MockClient) Scheme() *runtime.Scheme { + panic("implement me") +} + +func (m *MockClient) RESTMapper() meta.RESTMapper { + panic("implement me") +} + +type MockContext struct { +} + +func (m *MockContext) Deadline() (deadline time.Time, ok bool) { + panic("implement me") +} + +func (m *MockContext) Done() <-chan struct{} { + panic("implement me") +} + +func (m *MockContext) Err() error { + panic("implement me") +} + +func (m *MockContext) Value(key interface{}) interface{} { + panic("implement me") +} + +type MockLogger struct { + WithValuesKeysAndValues []interface{} + WithValuesCalled bool + WithNameValue string + WithNameCalled bool + loggedMessage string + keysAndValues []interface{} + logFunction func(msg string, keysAndValues ...interface{}) + logLevelCalled string + level int +} + +func (m *MockLogger) Enabled() bool { + return true +} + +func (m *MockLogger) Info(msg string, keysAndValues ...interface{}) { + m.loggedMessage = msg + m.keysAndValues = keysAndValues + m.logLevelCalled = "Info" + if m.logFunction != nil { + m.logFunction(msg, keysAndValues) + return + } +} + +func (m *MockLogger) Error(err error, msg string, keysAndValues ...interface{}) { + m.loggedMessage = msg + m.keysAndValues = keysAndValues + m.logLevelCalled = "Error" + if m.logFunction != nil { + m.logFunction(msg, keysAndValues) + return + } +} + +func (m *MockLogger) V(level int) logr.Logger { + m.level = level + return m +} + +func (m *MockLogger) WithValues(keysAndValues ...interface{}) logr.Logger { + m.WithValuesCalled = true + for i := 0; i < len(keysAndValues); i++ { + m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues[i]) + } + m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues) + + return m +} + +func (m *MockLogger) WithName(name string) logr.Logger { + m.WithNameCalled = true + m.WithNameValue = name + return m +} + +type MockController struct { + setupWithManagerWasCalled bool +} + +func (m *MockController) SetupWithManager(manager manager.Manager) error { + m.setupWithManagerWasCalled = true + return nil +} + +type MockManager struct { + Log logr.Logger + addHealthzCheckFunction func(name string, check healthz.Checker) error + addHealthzCheckWasCalled bool + addReadyzCheckFunction func(name string, check healthz.Checker) error + addReadyzCheckWasCalled bool + startFunction func(ctx context.Context) error + startWasCalled bool + builder *scheme.Builder + Fields []interface{} + runnable manager.Runnable +} + +func (m *MockManager) Add(runnable manager.Runnable) error { + m.runnable = runnable + + return nil +} + +func (m *MockManager) Elected() <-chan struct{} { + panic("implement me") +} + +func (m *MockManager) SetFields(i interface{}) error { + if m.Fields == nil { + m.Fields = make([]interface{}, 0) + } + m.Fields = append(m.Fields, i) + + return nil +} + +func (m *MockManager) AddMetricsExtraHandler(path string, handler http.Handler) error { + panic("implement me") +} + +func (m *MockManager) AddHealthzCheck(name string, check healthz.Checker) error { + m.addHealthzCheckWasCalled = true + if m.addHealthzCheckFunction == nil { + return nil + } + return m.addHealthzCheckFunction(name, check) +} + +func (m *MockManager) AddReadyzCheck(name string, check healthz.Checker) error { + m.addReadyzCheckWasCalled = true + if m.addReadyzCheckFunction == nil { + return nil + } + + return m.addReadyzCheckFunction(name, check) +} + +func (m *MockManager) Start(ctx context.Context) error { + m.startWasCalled = true + if m.startFunction == nil { + return nil + } + return m.startFunction(ctx) +} + +func (m *MockManager) GetConfig() *rest.Config { + return &rest.Config{} +} + +func (m *MockManager) GetScheme() *runtime.Scheme { + scheme, _ := m.builder.Build() + return scheme +} + +func (m *MockManager) GetClient() client.Client { + return nil +} + +func (m *MockManager) GetFieldIndexer() client.FieldIndexer { + panic("implement me") +} + +func (m *MockManager) GetCache() cache.Cache { + panic("implement me") +} + +func (m *MockManager) GetEventRecorderFor(name string) record.EventRecorder { + panic("implement me") +} + +func (m *MockManager) GetRESTMapper() meta.RESTMapper { + panic("implement me") +} + +func (m *MockManager) GetAPIReader() client.Reader { + panic("implement me") +} + +func (m *MockManager) GetWebhookServer() *webhook.Server { + panic("implement me") +} + +func (m *MockManager) GetLogger() logr.Logger { + return m.Log +} + +type MockRunFunctionParameters struct { + options zap.Options + metricsAddress string + healthProbeAddress string + enableLeaderElection bool +} + +type MockDriver struct { + parseCommandLineCalled bool + parseCommandLineFunction func() (string, bool, string, zap.Options, error) + runCalled bool + runFunction func(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) + runFunctionParameters MockRunFunctionParameters + newManagerCalled bool + newManagerFunction func(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) + instantiateControllersCalled bool + instantiateControllersFunction func(mgr manager.Manager) []controllers.ManagedController + setupControllerWithManagerCalled bool + setupControllersFunction func(controller controllers.ManagedController, manager manager.Manager) error +} + +func (m *MockDriver) parseCommandLine() (string, bool, string, zap.Options, error) { + m.parseCommandLineCalled = true + if m.parseCommandLineFunction == nil { + return "", true, "", zap.Options{}, nil + } + + return m.parseCommandLineFunction() +} + +func (m *MockDriver) run(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) { + m.runCalled = true + m.runFunctionParameters = MockRunFunctionParameters{ + options: opts, + metricsAddress: metricsAddress, + healthProbeAddress: healthProbeAddress, + enableLeaderElection: enableLeaderElection, + } + if m.runFunction == nil { + return 0, nil + } + + return m.runFunction(opts, metricsAddress, healthProbeAddress, enableLeaderElection) +} + +func (m *MockDriver) newManager(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) { + m.newManagerCalled = true + if m.newManagerFunction == nil { + return &MockManager{}, nil + } + + return m.newManagerFunction(metricsAddress, healthProbeAddress, enableLeaderElection) +} + +func (m *MockDriver) instantiateControllers(mgr manager.Manager) []controllers.ManagedController { + m.instantiateControllersCalled = true + + if m.instantiateControllersFunction == nil { + return []controllers.ManagedController{ + &MockController{}, + } + } + + return m.instantiateControllersFunction(mgr) +} + +func (m *MockDriver) setupControllerWithManager(controller controllers.ManagedController, manager manager.Manager) error { + m.setupControllerWithManagerCalled = true + + if m.setupControllersFunction == nil { + return nil + } + + return m.setupControllersFunction(controller, manager) +} + +type MockError struct { + message string +} + +func (m *MockError) Error() string { + if m.message == "" { + m.message = "mock Error" + } + return m.message +} + +type MockGitlabClient struct { + newClientFunction func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) + listGroupsFunction func(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) + listGroupsByNameFunction func(name string) ([]*gitlab.Group, *gitlab.Response, error) + createGroupFunction func(name string, description string) (*gitlab.Group, *gitlab.Response, error) + updateGroupFunction func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) +} + +func (m *MockGitlabClient) CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return m.createGroupFunction(name, description) +} + +func (m *MockGitlabClient) UpdateGroup(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return m.updateGroupFunction(id, name, description) +} + +func (m *MockGitlabClient) ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return m.listGroupsByNameFunction(name) +} + +func (m *MockGitlabClient) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return m.newClientFunction(token, options...) +} + +func (m *MockGitlabClient) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { + return m.listGroupsFunction(opt, options...) +} -- GitLab From 600d8c03cb39d4c5e43311cb9b823953ca251707 Mon Sep 17 00:00:00 2001 From: project_646_bot Date: Thu, 17 Jun 2021 10:13:41 -1000 Subject: [PATCH 09/16] fix lint --- .../fortify/fortifycredential_controller.go | 8 ++++---- .../fortifypipelineconfiguration_controller.go | 15 ++++++--------- .../twistlock/twistlockcredential_controller.go | 8 ++++---- .../twistlockpipelineconfiguration_controller.go | 14 ++++++-------- driver.go | 8 ++++---- driver_test.go | 8 ++++---- 6 files changed, 28 insertions(+), 33 deletions(-) diff --git a/controllers/fortify/fortifycredential_controller.go b/controllers/fortify/fortifycredential_controller.go index 3c2165e..030a99b 100644 --- a/controllers/fortify/fortifycredential_controller.go +++ b/controllers/fortify/fortifycredential_controller.go @@ -27,8 +27,8 @@ import ( fortifyv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/fortify/v1alpha1" ) -// FortifyCredentialReconciler reconciles a FortifyCredential object -type FortifyCredentialReconciler struct { +// CredentialReconciler reconciles a FortifyCredential object +type CredentialReconciler struct { client.Client Log logr.Logger Scheme *runtime.Scheme @@ -47,7 +47,7 @@ type FortifyCredentialReconciler struct { // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.8.3/pkg/reconcile -func (r *FortifyCredentialReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +func (r *CredentialReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = r.Log.WithValues("gitlabcredentials", req.NamespacedName) // your logic here @@ -55,7 +55,7 @@ func (r *FortifyCredentialReconciler) Reconcile(ctx context.Context, req ctrl.Re } // SetupWithManager sets up the controller with the Manager. -func (r *FortifyCredentialReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *CredentialReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&fortifyv1alpha1.FortifyCredential{}). Complete(r) diff --git a/controllers/fortify/fortifypipelineconfiguration_controller.go b/controllers/fortify/fortifypipelineconfiguration_controller.go index 8db3d6b..94b0eeb 100644 --- a/controllers/fortify/fortifypipelineconfiguration_controller.go +++ b/controllers/fortify/fortifypipelineconfiguration_controller.go @@ -36,8 +36,8 @@ import ( const finalizerName = "fortify.valkyrie.dso.mil/fortify_finalizer" -// FortifyPipelineConfigurationReconciler reconciles a FortifyPipelineConfiguration object -type FortifyPipelineConfigurationReconciler struct { +// PipelineConfigurationReconciler reconciles a FortifyPipelineConfiguration object +type PipelineConfigurationReconciler struct { client.Client Log logr.Logger Scheme *runtime.Scheme @@ -49,7 +49,7 @@ type FortifyPipelineConfigurationReconciler struct { // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -func (r *FortifyPipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) { +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") @@ -200,8 +200,6 @@ func (r *FortifyPipelineConfigurationReconciler) Reconcile(ctx context.Context, log.Info("successfully created a new Fortify project: " + fortifyConfig.Spec.ProjectName) } - return - } else { // The object is being deleted if utils.ContainsString(fortifyConfig.GetFinalizers(), finalizerName) { @@ -227,10 +225,9 @@ func (r *FortifyPipelineConfigurationReconciler) Reconcile(ctx context.Context, return } } - - // Stop reconciliation as the item is being deleted - return } + + return } func createBulkUpdateRequest(serverAPIURL string, versionID string, language string) *fortify.BulkUpdateRequest { @@ -444,7 +441,7 @@ func createBulkUpdateRequest(serverAPIURL string, versionID string, language str } // SetupWithManager sets up the controller with the Manager. -func (r *FortifyPipelineConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *PipelineConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error { pred := predicate.GenerationChangedPredicate{} return ctrl.NewControllerManagedBy(mgr). For(&fortifyv1alpha1.FortifyPipelineConfiguration{}). diff --git a/controllers/twistlock/twistlockcredential_controller.go b/controllers/twistlock/twistlockcredential_controller.go index 429d53e..876900c 100644 --- a/controllers/twistlock/twistlockcredential_controller.go +++ b/controllers/twistlock/twistlockcredential_controller.go @@ -28,8 +28,8 @@ import ( twistlockv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/twistlock/v1alpha1" ) -// TwistlockCredentialReconciler reconciles a TwistlockCredential object -type TwistlockCredentialReconciler struct { +// CredentialReconciler reconciles a TwistlockCredential object +type CredentialReconciler struct { client.Client Log logr.Logger Scheme *runtime.Scheme @@ -41,7 +41,7 @@ type TwistlockCredentialReconciler struct { // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -func (r *TwistlockCredentialReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +func (r *CredentialReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = log.FromContext(ctx) // your logic here @@ -50,7 +50,7 @@ func (r *TwistlockCredentialReconciler) Reconcile(ctx context.Context, req ctrl. } // SetupWithManager sets up the controller with the Manager. -func (r *TwistlockCredentialReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *CredentialReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&twistlockv1alpha1.TwistlockCredential{}). Complete(r) diff --git a/controllers/twistlock/twistlockpipelineconfiguration_controller.go b/controllers/twistlock/twistlockpipelineconfiguration_controller.go index 9b5f4c7..88f570c 100644 --- a/controllers/twistlock/twistlockpipelineconfiguration_controller.go +++ b/controllers/twistlock/twistlockpipelineconfiguration_controller.go @@ -41,8 +41,8 @@ const ( finalizerName = "gitlab.valkyrie.dso.mil/twistlock_finalizer" ) -// TwistlockPipelineConfigurationReconciler reconciles a TwistlockPipelineConfiguration object -type TwistlockPipelineConfigurationReconciler struct { +// PipelineConfigurationReconciler reconciles a TwistlockPipelineConfiguration object +type PipelineConfigurationReconciler struct { client.Client Log logr.Logger Scheme *runtime.Scheme @@ -57,7 +57,7 @@ type TwistlockPipelineConfigurationReconciler struct { // // 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) (result ctrl.Result, err error) { +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") @@ -221,7 +221,6 @@ func (r *TwistlockPipelineConfigurationReconciler) Reconcile(ctx context.Context log.Info("successfully created a new registry spec: " + twistlockConfig.Spec.Repository) } - return } else { // The object is being deleted if utils.ContainsString(twistlockConfig.GetFinalizers(), finalizerName) { @@ -265,14 +264,13 @@ func (r *TwistlockPipelineConfigurationReconciler) Reconcile(ctx context.Context return } } - - // Stop reconciliation as the item is being deleted - return } + + return } // SetupWithManager sets up the controller with the Manager. -func (r *TwistlockPipelineConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *PipelineConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error { pred := predicate.GenerationChangedPredicate{} return ctrl.NewControllerManagedBy(mgr). For(&twistlockv1alpha1.TwistlockPipelineConfiguration{}). diff --git a/driver.go b/driver.go index 9aab7bd..a7f8ef8 100755 --- a/driver.go +++ b/driver.go @@ -147,22 +147,22 @@ func (d driverImpl) instantiateControllers(mgr manager.Manager) []controllers.Ma Log: ctrl.Log.WithName("controllers").WithName("gitlab").WithName("Pipeline"), Scheme: mgr.GetScheme(), }, - &fortifycontrollers.FortifyCredentialReconciler{ + &fortifycontrollers.CredentialReconciler{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("fortify").WithName("FortifyCredential"), Scheme: mgr.GetScheme(), }, - &fortifycontrollers.FortifyPipelineConfigurationReconciler{ + &fortifycontrollers.PipelineConfigurationReconciler{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("fortify").WithName("FortifyPipelineConfiguration"), Scheme: mgr.GetScheme(), }, - &twistlockcontrollers.TwistlockPipelineConfigurationReconciler{ + &twistlockcontrollers.PipelineConfigurationReconciler{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("twistlock").WithName("TwistlockPipelineConfiguration"), Scheme: mgr.GetScheme(), }, - &twistlockcontrollers.TwistlockCredentialReconciler{ + &twistlockcontrollers.CredentialReconciler{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("twistlock").WithName("TwistlockCredential"), Scheme: mgr.GetScheme(), diff --git a/driver_test.go b/driver_test.go index 753a630..ecc86e8 100755 --- a/driver_test.go +++ b/driver_test.go @@ -36,16 +36,16 @@ var _ = Describe("instantiateControllers", func() { Expect(controllers[4]).To(BeAssignableToTypeOf(&gitlab.PipelineReconciler{})) }) It("Should create a Fortify Credential Controller", func() { - Expect(controllers[5]).To(BeAssignableToTypeOf(&fortify.FortifyCredentialReconciler{})) + Expect(controllers[5]).To(BeAssignableToTypeOf(&fortify.CredentialReconciler{})) }) It("Should create a Fortify Configuration Controller", func() { - Expect(controllers[6]).To(BeAssignableToTypeOf(&fortify.FortifyPipelineConfigurationReconciler{})) + Expect(controllers[6]).To(BeAssignableToTypeOf(&fortify.PipelineConfigurationReconciler{})) }) It("Should create a Twistlock Configuration Controller", func() { - Expect(controllers[7]).To(BeAssignableToTypeOf(&twistlock.TwistlockPipelineConfigurationReconciler{})) + Expect(controllers[7]).To(BeAssignableToTypeOf(&twistlock.PipelineConfigurationReconciler{})) }) It("Should create a Twistlock credential Controller", func() { - Expect(controllers[8]).To(BeAssignableToTypeOf(&twistlock.TwistlockCredentialReconciler{})) + Expect(controllers[8]).To(BeAssignableToTypeOf(&twistlock.CredentialReconciler{})) }) It("Should create a SonarQube Configuration Controller", func() { Expect(controllers[9]).To(BeAssignableToTypeOf(&gitlab.SonarqubePipelineConfigurationReconciler{})) -- GitLab From f6ba479374fab3577eb1f1929c9c9c6394afaf8f Mon Sep 17 00:00:00 2001 From: project_646_bot Date: Fri, 18 Jun 2021 10:56:27 -1000 Subject: [PATCH 10/16] add unit tests for fortify client --- clients/fortify/client.go | 4 +- clients/fortify/client_test.go | 495 +++++++++--------- .../fortify/fortify_api_adhoc_test.go | 277 ++++++++++ 3 files changed, 520 insertions(+), 256 deletions(-) create mode 100644 integration-tests/fortify/fortify_api_adhoc_test.go diff --git a/clients/fortify/client.go b/clients/fortify/client.go index 3cd7e79..5d83114 100644 --- a/clients/fortify/client.go +++ b/clients/fortify/client.go @@ -20,12 +20,10 @@ type Client struct { //NewClient creates a new API client for fortify func NewClient(serverURL string, apiToken string) *Client { - client := clients.CreatHTTPClient() - return &Client{ serverAPIURL: serverURL + apiVersion, apiToken: apiToken, - httpClient: client, + httpClient: &http.Client{}, } } diff --git a/clients/fortify/client_test.go b/clients/fortify/client_test.go index b0346f5..88e5cfe 100644 --- a/clients/fortify/client_test.go +++ b/clients/fortify/client_test.go @@ -1,274 +1,263 @@ package fortify import ( - "log" + "errors" + "github.com/jarcoal/httpmock" "testing" ) -const ( - serverURL = "https://fortify.il2.dso.mil" - apiToken = "token" -) - -func TestRegisterProject(t *testing.T) { - client := NewClient(serverURL, apiToken) - projectVersions, err := client.SearchProjectVersions(&ProjectSearch{ - ResultLimit: 1, - ProjectName: "platform-one-devops-hello-pipeline-maven-world", - }) - if err != nil { - log.Printf("%v", err) +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()) } - log.Printf("count = %d", len(projectVersions.Data)) } -func TestVersionCreate(t *testing.T) { - client := NewClient(serverURL, apiToken) - 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", - } +func TestClient_SearchProjectVersions(t *testing.T) { + //testHTTPClient := &http.Client{} - resp, err := client.CreateProjectVersion(request) - if err != nil { - log.Printf("%v", err) + 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}, } - log.Println(resp.Data.ID) + // 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 TestUpdateProject(t *testing.T) { - versionID := "841" - serverAPIURL := "https://fortify.il2.dso.mil/api/v1" - language := "javascript" - //make the request data - request := BulkUpdateRequest{Requests: []UpdateRequest{ - { - URI: serverAPIURL + "/projectVersions/" + versionID + "/attributes", - HTTPVerb: "PUT", - PostData: []PostDataRequest{ - { - AttributeDefinitionID: 1, - Values: []ValueRequest{ - { - GUID: "High", - }, - }, - }, - { - AttributeDefinitionID: 5, - Values: []ValueRequest{ - { - GUID: "Active", - }, - }, - }, - { - AttributeDefinitionID: 6, - Values: []ValueRequest{ - { - GUID: "Internal", - }, - }, - }, - { - AttributeDefinitionID: 7, - Values: []ValueRequest{ - { - GUID: "internalnetwork", - }, - }, - }, - { - AttributeDefinitionID: 8, - Values: []ValueRequest{ - { - GUID: "App", - }, - }, - }, - { - AttributeDefinitionID: 9, - Values: []ValueRequest{ - { - GUID: "NA", - }, - }, - }, - { - AttributeDefinitionID: 10, - Values: []ValueRequest{ - { - GUID: "WA", - }, - }, - }, - { - AttributeDefinitionID: 11, - Values: []ValueRequest{ - { - GUID: language, - }, +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", }, - }, - { - AttributeDefinitionID: 12, - Values: []ValueRequest{ - { - GUID: "None", - }, + 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", }, - }, - }, - }, - { - URI: serverAPIURL + "/projectVersions/" + versionID + "/responsibilities", - HTTPVerb: "PUT", - PostData: []PostDataRequest{ - { - ResponsibilityGUID: "projectmanager", - }, - { - ResponsibilityGUID: "securitychampion", - }, - { - ResponsibilityGUID: "developmentmanager", - }, - { - ResponsibilityGUID: "superuser", - }, - }, - }, - { - URI: serverAPIURL + "/projectVersions/" + versionID + "?hideProgress=true", - HTTPVerb: "PUT", - PostData: ProjectVersionCreateRequest{ - Committed: true, - }, - }, - { - URI: serverAPIURL + "/projectVersions/" + versionID + "/resultProcessingRules", - HTTPVerb: "PUT", - PostData: []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 := NewClient(serverURL, apiToken) - err := client.BulkUpdate(request) - if err != nil { - log.Printf("error in update: %v", err) + 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 TestDeleteProjectVersion(t *testing.T) { - client := NewClient(serverURL, apiToken) - err := client.DeleteProjectVersion(841) - if err != nil { - log.Printf("%v", err) + +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") + } + }) + } + } +} \ No newline at end of file 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 0000000..02f9c4b --- /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) + } +} -- GitLab From 78af82405a33b3519997f118e4b6f8c4f4f2085c Mon Sep 17 00:00:00 2001 From: project_646_bot Date: Mon, 21 Jun 2021 14:36:39 -1000 Subject: [PATCH 11/16] add unit tests for fortify and twistlock controllers --- .../fortifycredential_scaffold_test.go | 260 +++ ...tifypipelineconfiguration_scaffold_test.go | 257 +++ clients/fortify/client_test.go | 11 +- clients/twistlock/client.go | 2 +- .../fortify/fortifycredential_controller.go | 9 +- .../fortifycredential_controller_test.go | 65 + ...fortifypipelineconfiguration_controller.go | 13 +- ...fypipelineconfiguration_controller_test.go | 1008 ++++++++++++ controllers/fortify/mocks_test.go | 394 +++++ controllers/twistlock/mocks_test.go | 394 +++++ .../twistlockcredential_controller.go | 4 +- .../twistlockcredential_controller_test.go | 65 + ...istlockpipelineconfiguration_controller.go | 10 +- ...ckpipelineconfiguration_controller_test.go | 1464 ++++++++++++++++- 14 files changed, 3877 insertions(+), 79 deletions(-) create mode 100644 apis/fortify/v1alpha1/fortifycredential_scaffold_test.go create mode 100644 apis/fortify/v1alpha1/fortifypipelineconfiguration_scaffold_test.go create mode 100755 controllers/fortify/fortifycredential_controller_test.go create mode 100755 controllers/fortify/fortifypipelineconfiguration_controller_test.go create mode 100755 controllers/fortify/mocks_test.go create mode 100755 controllers/twistlock/mocks_test.go create mode 100755 controllers/twistlock/twistlockcredential_controller_test.go mode change 100644 => 100755 controllers/twistlock/twistlockpipelineconfiguration_controller_test.go diff --git a/apis/fortify/v1alpha1/fortifycredential_scaffold_test.go b/apis/fortify/v1alpha1/fortifycredential_scaffold_test.go new file mode 100644 index 0000000..a38a8d7 --- /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/fortifypipelineconfiguration_scaffold_test.go b/apis/fortify/v1alpha1/fortifypipelineconfiguration_scaffold_test.go new file mode 100644 index 0000000..10939e2 --- /dev/null +++ b/apis/fortify/v1alpha1/fortifypipelineconfiguration_scaffold_test.go @@ -0,0 +1,257 @@ +package v1alpha1 + +import ( + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// testVarsFortifyPipelineConfiguration - Reusable test variables +type testVarsFortifyPipelineConfiguration struct { + testKind string + testAPIVersion string + testSpec string + testStatus int + + expectedKind string + expectedAPIVersion string + expectedSpec string + expectedStatus int + + testObject1 FortifyPipelineConfiguration + testObject2 FortifyPipelineConfiguration + + objectItems1 []FortifyPipelineConfiguration + objectList1 FortifyPipelineConfigurationList + + objectItems2 []FortifyPipelineConfiguration + objectList2 FortifyPipelineConfigurationList + + // leave scaffold Foo value for testing? + testObjectSpec1 FortifyPipelineConfigurationSpec + + // leave scaffold Foo value for testing? + testObjectStatus1 FortifyPipelineConfigurationStatus +} + +// initVarsFortifyPipelineConfiguration - intialize test variables +func initVarsFortifyPipelineConfiguration() testVarsFortifyPipelineConfiguration { + testVars := testVarsFortifyPipelineConfiguration{} + + testVars.testKind = "TestKind" + testVars.testAPIVersion = "v22" + testVars.testSpec = "valkyrie" + testVars.testStatus = 88 + + 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 = FortifyPipelineConfiguration{TypeMeta: object1MetaType} + + var object2MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind2", APIVersion: "V99"} + testVars.testObject2 = FortifyPipelineConfiguration{TypeMeta: object2MetaType} + + var objectList1MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind_List", APIVersion: "V12"} + var objectItems1 []FortifyPipelineConfiguration = []FortifyPipelineConfiguration{testVars.testObject1, testVars.testObject2} + // test_object_list = FortifyPipelineConfigurationList(objectList1MetaType,nil,object_items) + testVars.objectList1 = FortifyPipelineConfigurationList{TypeMeta: objectList1MetaType, Items: objectItems1} + + var objectList2MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind_List", APIVersion: "V12"} + var objectItems2 []FortifyPipelineConfiguration = []FortifyPipelineConfiguration{testVars.testObject2} + // test_object_list = FortifyPipelineConfigurationList(objectList1MetaType,nil,object_items) + testVars.objectList2 = FortifyPipelineConfigurationList{TypeMeta: objectList2MetaType, Items: objectItems2} + + // leave scaffold Foo value for testing? + testVars.testObjectSpec1 = FortifyPipelineConfigurationSpec{ProjectName: testVars.testSpec} + + // leave scaffold Foo value for testing? + testVars.testObjectStatus1 = FortifyPipelineConfigurationStatus{ProjectID: &testVars.testStatus} + + return testVars +} + +// TestGroupVarsFortifyPipelineConfiguration - +func TestGroupVarsFortifyPipelineConfiguration(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") +} + +// TestTypesFortifyPipelineConfiguration - +func TestTypesFortifyPipelineConfiguration(t *testing.T) { + lTestVars := initVarsFortifyPipelineConfiguration() + want := lTestVars.expectedAPIVersion + got := lTestVars.testObject1.APIVersion + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +// TestDeepCopyDeepCopyFortifyPipelineConfiguration - +func TestDeepCopyDeepCopyFortifyPipelineConfiguration(t *testing.T) { + lTestVars := initVarsFortifyPipelineConfiguration() + + 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 *FortifyPipelineConfiguration = nil + var val = nilTestPtr.DeepCopyObject() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + + t.Log("Success") +} + +// TestDeepCopyDeepCopyIntoFortifyPipelineConfiguration - +func TestDeepCopyDeepCopyIntoFortifyPipelineConfiguration(t *testing.T) { + lTestVars := initVarsFortifyPipelineConfiguration() + + 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") +} + +// TestDeepCopyDeepCopyObjectFortifyPipelineConfiguration - +func TestDeepCopyDeepCopyObjectFortifyPipelineConfiguration(t *testing.T) { + lTestVars := initVarsFortifyPipelineConfiguration() + + newRuntimeObject := lTestVars.testObject1.DeepCopyObject() + newObject := newRuntimeObject.(*FortifyPipelineConfiguration) + got := newObject.APIVersion + want := lTestVars.expectedAPIVersion + if got != want { + t.Errorf("got %s want %s", got, want) + } + t.Log("Success") +} + +// TestDeepCopyDeepCopyListFortifyPipelineConfiguration - +func TestDeepCopyDeepCopyListFortifyPipelineConfiguration(t *testing.T) { + lTestVars := initVarsFortifyPipelineConfiguration() + 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 *FortifyPipelineConfigurationList = nil + var val = nilTestPtr.DeepCopy() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + t.Log("Success") +} + +// TestDeepCopyDeepCopyIntoListFortifyPipelineConfiguration - +func TestDeepCopyDeepCopyIntoListFortifyPipelineConfiguration(t *testing.T) { + lTestVars := initVarsFortifyPipelineConfiguration() + + 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") +} + +// TestDeepCopyDeepCopyListObjectFortifyPipelineConfiguration - +func TestDeepCopyDeepCopyListObjectFortifyPipelineConfiguration(t *testing.T) { + lTestVars := initVarsFortifyPipelineConfiguration() + + newRuntimeObject := lTestVars.objectList1.DeepCopyObject() + newObject := newRuntimeObject.(*FortifyPipelineConfigurationList) + got := newObject.Items[0].APIVersion + want := lTestVars.expectedAPIVersion + if got != want { + t.Errorf("got %s want %s", got, want) + } + + var nilTestPtr *FortifyPipelineConfigurationList = nil + var val = nilTestPtr.DeepCopyObject() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + + t.Log("Success") +} + +// TestDeepCopyDeepCopySpecFortifyPipelineConfiguration - +func TestDeepCopyDeepCopySpecFortifyPipelineConfiguration(t *testing.T) { + lTestVars := initVarsFortifyPipelineConfiguration() + + newObjectList := lTestVars.testObjectSpec1.DeepCopy() + + got := newObjectList.ProjectName + want := lTestVars.expectedSpec + + if got != want { + t.Errorf("got %s want %s", got, want) + } + + var nilTestPtr *FortifyPipelineConfigurationSpec = nil + var val = nilTestPtr.DeepCopy() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + t.Log("Success") +} + +// TestDeepCopyDeepCopyStatusFortifyPipelineConfiguration - +func TestDeepCopyDeepCopyStatusFortifyPipelineConfiguration(t *testing.T) { + lTestVars := initVarsFortifyPipelineConfiguration() + + newObjectStatus := lTestVars.testObjectStatus1.DeepCopy() + + got := *newObjectStatus.ProjectID + want := lTestVars.expectedStatus + + if got != want { + t.Errorf("got %d want %d", got, want) + } + + // a typed pointer set to nil + var nilTestPtr *FortifyPipelineConfigurationStatus = nil + var val = nilTestPtr.DeepCopy() + if val != nil { + t.Errorf("got %s want %s", "not nil", "nil") + } + + t.Log("Success") +} diff --git a/clients/fortify/client_test.go b/clients/fortify/client_test.go index 88e5cfe..b76c71c 100644 --- a/clients/fortify/client_test.go +++ b/clients/fortify/client_test.go @@ -163,13 +163,12 @@ func TestClient_CreateProjectVersion(t *testing.T) { } } - func TestClient_DeleteProjectVersion(t *testing.T) { tests := []struct { - name string + name string versionID int - wantErr bool + wantErr bool }{ {name: "deleteProject", versionID: 88, wantErr: false}, {name: "deleteProject Error", versionID: 88, wantErr: true}, @@ -216,9 +215,9 @@ func TestClient_DeleteProjectVersion(t *testing.T) { func TestClient_BulkUpdate(t *testing.T) { tests := []struct { - name string + name string versionID int - wantErr bool + wantErr bool }{ {name: "bulkUpdate", versionID: 88, wantErr: false}, {name: "bulkUpdate Error", versionID: 88, wantErr: true}, @@ -260,4 +259,4 @@ func TestClient_BulkUpdate(t *testing.T) { }) } } -} \ No newline at end of file +} diff --git a/clients/twistlock/client.go b/clients/twistlock/client.go index 3adf0f0..3ddde37 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, diff --git a/controllers/fortify/fortifycredential_controller.go b/controllers/fortify/fortifycredential_controller.go index 030a99b..873f19f 100644 --- a/controllers/fortify/fortifycredential_controller.go +++ b/controllers/fortify/fortifycredential_controller.go @@ -40,15 +40,8 @@ type CredentialReconciler struct { // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the FortifyCredential 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.8.3/pkg/reconcile func (r *CredentialReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = r.Log.WithValues("gitlabcredentials", req.NamespacedName) + _ = r.Log.WithValues("fortifycredential", req.NamespacedName) // your logic here return ctrl.Result{}, nil diff --git a/controllers/fortify/fortifycredential_controller_test.go b/controllers/fortify/fortifycredential_controller_test.go new file mode 100755 index 0000000..baafeba --- /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 index 94b0eeb..74be8be 100644 --- a/controllers/fortify/fortifypipelineconfiguration_controller.go +++ b/controllers/fortify/fortifypipelineconfiguration_controller.go @@ -76,8 +76,9 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr 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 err = r.Status().Update(ctx, &fortifyConfig); err != nil { - log.Error(err, "error updating resource status") + if err2 := r.Status().Update(ctx, &fortifyConfig); err2 != nil { + log.Error(err2, "error updating resource status") + err = err2 return } return @@ -93,8 +94,9 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr 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 err = r.Status().Update(ctx, &fortifyConfig); err != nil { - log.Error(err, "error updating resource status") + if err2 := r.Status().Update(ctx, &fortifyConfig); err2 != nil { + log.Error(err2, "error updating resource status") + err = err2 return } return @@ -207,8 +209,7 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr log.Info("removing project from Fortify: " + fortifyConfig.Spec.ProjectName) existingProjectID := fortifyConfig.Status.ProjectID if existingProjectID != nil { - //TODO: uncomment for production - //err = fc.DeleteProjectVersion(*existingProjectID) + err = fc.DeleteProjectVersion(*existingProjectID) if err != nil { log.Error(err, "error deleting project from Fortify") return diff --git a/controllers/fortify/fortifypipelineconfiguration_controller_test.go b/controllers/fortify/fortifypipelineconfiguration_controller_test.go new file mode 100755 index 0000000..0adbbe6 --- /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 0000000..dfd0db9 --- /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/twistlock/mocks_test.go b/controllers/twistlock/mocks_test.go new file mode 100755 index 0000000..7f4ba1b --- /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/twistlockcredential_controller.go b/controllers/twistlock/twistlockcredential_controller.go index 876900c..4e42389 100644 --- a/controllers/twistlock/twistlockcredential_controller.go +++ b/controllers/twistlock/twistlockcredential_controller.go @@ -23,8 +23,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" - twistlockv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/twistlock/v1alpha1" ) @@ -42,7 +40,7 @@ type CredentialReconciler struct { // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. func (r *CredentialReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) + _ = r.Log.WithValues("twistlockcredential", req.NamespacedName) // your logic here diff --git a/controllers/twistlock/twistlockcredential_controller_test.go b/controllers/twistlock/twistlockcredential_controller_test.go new file mode 100755 index 0000000..368cbf3 --- /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 index 88f570c..8a5214d 100644 --- a/controllers/twistlock/twistlockpipelineconfiguration_controller.go +++ b/controllers/twistlock/twistlockpipelineconfiguration_controller.go @@ -85,8 +85,9 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr 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 err = r.Status().Update(ctx, &twistlockConfig); err != nil { - log.Error(err, "error updating resource status") + if err2 := r.Status().Update(ctx, &twistlockConfig); err2 != nil { + log.Error(err2, "error updating resource status") + err = err2 return } return @@ -102,8 +103,9 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr 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 err = r.Status().Update(ctx, &twistlockConfig); err != nil { - log.Error(err, "error updating resource status") + if err2 := r.Status().Update(ctx, &twistlockConfig); err2 != nil { + log.Error(err2, "error updating resource status") + err2 = err return } return diff --git a/controllers/twistlock/twistlockpipelineconfiguration_controller_test.go b/controllers/twistlock/twistlockpipelineconfiguration_controller_test.go old mode 100644 new mode 100755 index 0f62215..3bf74d4 --- a/controllers/twistlock/twistlockpipelineconfiguration_controller_test.go +++ b/controllers/twistlock/twistlockpipelineconfiguration_controller_test.go @@ -1,53 +1,1415 @@ package twistlock -//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")) -// }) -// }) -//}) +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()) + }) + }) + }) +}) +*/ -- GitLab From 2d79d2c2ab8d68a03e8630f6c724d78066d28230 Mon Sep 17 00:00:00 2001 From: project_646_bot Date: Mon, 21 Jun 2021 14:53:10 -1000 Subject: [PATCH 12/16] refactor to fix sonarqube findings --- ...fortifypipelineconfiguration_controller.go | 21 ++++++++++++------- ...istlockpipelineconfiguration_controller.go | 10 ++++++--- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/controllers/fortify/fortifypipelineconfiguration_controller.go b/controllers/fortify/fortifypipelineconfiguration_controller.go index 74be8be..36882d7 100644 --- a/controllers/fortify/fortifypipelineconfiguration_controller.go +++ b/controllers/fortify/fortifypipelineconfiguration_controller.go @@ -36,6 +36,11 @@ import ( 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 @@ -77,7 +82,7 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr 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, "error updating resource status") + log.Error(err2, statusUpdateError) err = err2 return } @@ -95,7 +100,7 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr 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, "error updating resource status") + log.Error(err2, statusUpdateError) err = err2 return } @@ -154,7 +159,7 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr fortifyConfig.Status.LastUpdatedTime = &updatedTime fortifyConfig.Status.State = "project successfully updated" if err = r.Status().Update(ctx, &fortifyConfig); err != nil { - log.Error(err, "error updating resource status") + log.Error(err, statusUpdateError) return } log.Info("successfully updated Fortify project: " + fortifyConfig.Spec.ProjectName) @@ -196,7 +201,7 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr fortifyConfig.Status.CreatedTime = &createdTime fortifyConfig.Status.State = "project successfully created" if err = r.Status().Update(ctx, &fortifyConfig); err != nil { - log.Error(err, "error updating resource status") + log.Error(err, statusUpdateError) return } log.Info("successfully created a new Fortify project: " + fortifyConfig.Spec.ProjectName) @@ -234,7 +239,7 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr func createBulkUpdateRequest(serverAPIURL string, versionID string, language string) *fortify.BulkUpdateRequest { return &fortify.BulkUpdateRequest{Requests: []fortify.UpdateRequest{ { - URI: serverAPIURL + "/projectVersions/" + versionID + "/attributes", + URI: serverAPIURL + projectVersionSubUrl + versionID + "/attributes", HTTPVerb: "PUT", PostData: []fortify.PostDataRequest{ { @@ -312,7 +317,7 @@ func createBulkUpdateRequest(serverAPIURL string, versionID string, language str }, }, { - URI: serverAPIURL + "/projectVersions/" + versionID + "/responsibilities", + URI: serverAPIURL + projectVersionSubUrl + versionID + "/responsibilities", HTTPVerb: "PUT", PostData: []fortify.PostDataRequest{ { @@ -330,14 +335,14 @@ func createBulkUpdateRequest(serverAPIURL string, versionID string, language str }, }, { - URI: serverAPIURL + "/projectVersions/" + versionID + "?hideProgress=true", + URI: serverAPIURL + projectVersionSubUrl + versionID + "?hideProgress=true", HTTPVerb: "PUT", PostData: fortify.ProjectVersionCreateRequest{ Committed: true, }, }, { - URI: serverAPIURL + "/projectVersions/" + versionID + "/resultProcessingRules", + URI: serverAPIURL + projectVersionSubUrl + versionID + "/resultProcessingRules", HTTPVerb: "PUT", PostData: []fortify.PostDataRequest{ { diff --git a/controllers/twistlock/twistlockpipelineconfiguration_controller.go b/controllers/twistlock/twistlockpipelineconfiguration_controller.go index 8a5214d..612d3be 100644 --- a/controllers/twistlock/twistlockpipelineconfiguration_controller.go +++ b/controllers/twistlock/twistlockpipelineconfiguration_controller.go @@ -41,6 +41,10 @@ const ( finalizerName = "gitlab.valkyrie.dso.mil/twistlock_finalizer" ) +const ( + statusUpdateError = "error updating resource status" +) + // PipelineConfigurationReconciler reconciles a TwistlockPipelineConfiguration object type PipelineConfigurationReconciler struct { client.Client @@ -86,7 +90,7 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr 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, "error updating resource status") + log.Error(err2, statusUpdateError) err = err2 return } @@ -104,7 +108,7 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr 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, "error updating resource status") + log.Error(err2, statusUpdateError) err2 = err return } @@ -123,7 +127,7 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr log.Error(err, "unable to fetch password secret from fortify credential") twistlockConfig.Status.State = "password secret from Twistlock credential not found." if err = r.Status().Update(ctx, &twistlockConfig); err != nil { - log.Error(err, "error updating resource status") + log.Error(err, statusUpdateError) return } return -- GitLab From e63610eaebef5579383690b2bcfa2ee8ab3eac37 Mon Sep 17 00:00:00 2001 From: project_646_bot Date: Mon, 21 Jun 2021 14:57:32 -1000 Subject: [PATCH 13/16] fix trufflehog --- .../twistlockpipelineconfiguration_controller.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/controllers/twistlock/twistlockpipelineconfiguration_controller.go b/controllers/twistlock/twistlockpipelineconfiguration_controller.go index 612d3be..24e21bd 100644 --- a/controllers/twistlock/twistlockpipelineconfiguration_controller.go +++ b/controllers/twistlock/twistlockpipelineconfiguration_controller.go @@ -116,26 +116,26 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr } username := string(usernameSecret.Data[twistlockCredential.Spec.UsernameSecRef.Key]) - //Get the password from secret - var passwordSecretName = types.NamespacedName{ + //Get pwd from secret + var pwdSecretName = types.NamespacedName{ Namespace: req.Namespace, Name: twistlockCredential.Spec.PasswordSecRef.Name, } - var passwordSecret v1.Secret - if err = r.Get(ctx, passwordSecretName, &passwordSecret); err != nil { - log.Error(err, "unable to fetch password secret from fortify credential") - twistlockConfig.Status.State = "password secret from Twistlock credential not found." + 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 } - password := string(passwordSecret.Data[twistlockCredential.Spec.PasswordSecRef.Key]) + pwd := string(pwdSecret.Data[twistlockCredential.Spec.PasswordSecRef.Key]) //create Twistlock client for API calls - tlc := twistlock.NewClient(twistlockCredential.Spec.ServerURL, username, password) + tlc := twistlock.NewClient(twistlockCredential.Spec.ServerURL, username, pwd) // examine DeletionTimestamp to determine if object is under deletion if twistlockConfig.ObjectMeta.DeletionTimestamp.IsZero() { -- GitLab From 2139b36c562a5ec9abf6a2f4257489d8ff3543c2 Mon Sep 17 00:00:00 2001 From: project_646_bot Date: Mon, 21 Jun 2021 15:11:07 -1000 Subject: [PATCH 14/16] fix pipeline issues --- README.md | 10 +++++----- .../fortifypipelineconfiguration_controller.go | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b246098..fbdf7b4 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ package integration - `go test -v -tags "integration" ./integration-tests/...` ### Twistlock Controller Setup -1) Create a secret that holds the username and password for Twistlock API +1) Create a secret that holds the credential for Twistlock API ```yaml apiVersion: v1 kind: Secret @@ -52,8 +52,8 @@ metadata: name: twistlock-secret type: Opaque data: - username: eW91cl90b2tlbg== - password: eW91cl90b2tlbg== + usr: eW91cl90b2tlbg== + pwd: eW91cl90b2tlbg== ``` 2) Create a TwistlockCredential resource ```yaml @@ -65,10 +65,10 @@ spec: serverUrl: "https://twistlock.bigbang.dev" usernameSecretRef: name: twistlock-secret - key: username + key: usr passwordSecretRef: name: twistlock-secret - key: password + key: pwd ``` 3) Create a TwistlockPipelineConfiguration resource ```yaml diff --git a/controllers/fortify/fortifypipelineconfiguration_controller.go b/controllers/fortify/fortifypipelineconfiguration_controller.go index 36882d7..b3e4f4a 100644 --- a/controllers/fortify/fortifypipelineconfiguration_controller.go +++ b/controllers/fortify/fortifypipelineconfiguration_controller.go @@ -37,8 +37,8 @@ import ( const finalizerName = "fortify.valkyrie.dso.mil/fortify_finalizer" const ( - projectVersionSubUrl = "/projectVersions/" - statusUpdateError = "error updating resource status" + projectVersionSubURL = "/projectVersions/" + statusUpdateError = "error updating resource status" ) // PipelineConfigurationReconciler reconciles a FortifyPipelineConfiguration object @@ -239,7 +239,7 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr func createBulkUpdateRequest(serverAPIURL string, versionID string, language string) *fortify.BulkUpdateRequest { return &fortify.BulkUpdateRequest{Requests: []fortify.UpdateRequest{ { - URI: serverAPIURL + projectVersionSubUrl + versionID + "/attributes", + URI: serverAPIURL + projectVersionSubURL + versionID + "/attributes", HTTPVerb: "PUT", PostData: []fortify.PostDataRequest{ { @@ -317,7 +317,7 @@ func createBulkUpdateRequest(serverAPIURL string, versionID string, language str }, }, { - URI: serverAPIURL + projectVersionSubUrl + versionID + "/responsibilities", + URI: serverAPIURL + projectVersionSubURL + versionID + "/responsibilities", HTTPVerb: "PUT", PostData: []fortify.PostDataRequest{ { @@ -335,14 +335,14 @@ func createBulkUpdateRequest(serverAPIURL string, versionID string, language str }, }, { - URI: serverAPIURL + projectVersionSubUrl + versionID + "?hideProgress=true", + URI: serverAPIURL + projectVersionSubURL + versionID + "?hideProgress=true", HTTPVerb: "PUT", PostData: fortify.ProjectVersionCreateRequest{ Committed: true, }, }, { - URI: serverAPIURL + projectVersionSubUrl + versionID + "/resultProcessingRules", + URI: serverAPIURL + projectVersionSubURL + versionID + "/resultProcessingRules", HTTPVerb: "PUT", PostData: []fortify.PostDataRequest{ { -- GitLab From b5076aa443b7182427fd57391fb2f1a691df2db7 Mon Sep 17 00:00:00 2001 From: project_646_bot Date: Mon, 21 Jun 2021 15:29:13 -1000 Subject: [PATCH 15/16] fix sonarqube findings --- ...fortifypipelineconfiguration_controller.go | 213 ++++++++-------- ...istlockpipelineconfiguration_controller.go | 227 +++++++++--------- 2 files changed, 229 insertions(+), 211 deletions(-) diff --git a/controllers/fortify/fortifypipelineconfiguration_controller.go b/controllers/fortify/fortifypipelineconfiguration_controller.go index b3e4f4a..a3cfda4 100644 --- a/controllers/fortify/fortifypipelineconfiguration_controller.go +++ b/controllers/fortify/fortifypipelineconfiguration_controller.go @@ -113,126 +113,135 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr // examine DeletionTimestamp to determine if object is under deletion if fortifyConfig.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(fortifyConfig.GetFinalizers(), finalizerName) { - controllerutil.AddFinalizer(&fortifyConfig, finalizerName) - if err = r.Update(ctx, &fortifyConfig); err != nil { - log.Error(err, "error adding finalizer to list") - return - } + err = r.handleCreateOrUpdate(ctx, fortifyConfig, log, fc) + } else { + err = r.handleDelete(ctx, fortifyConfig, log, fc) + } + + 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, - }) + //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 { + 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 search for existing projects in Fortify") + log.Error(err, "error updating fortify project: "+fortifyConfig.Spec.ProjectName) 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)) + //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) + } else { + //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", } - //if project already exists in Fortify, we update it - if existingVersionID != -1 { - 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 - } + var createResponse fortify.ProjectVersionCreateResponse + createResponse, err = fc.CreateProjectVersion(createRequest) + if err != nil { + log.Error(err, "error creating 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) - } else { - //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", - } + //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 + } - var createResponse fortify.ProjectVersionCreateResponse - createResponse, err = fc.CreateProjectVersion(createRequest) - if err != nil { - log.Error(err, "error creating fortify project: "+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 +} - //update project settings - bulkUpdateRequest := createBulkUpdateRequest(fc.GetServerAPIBaseURL(), strconv.Itoa(createResponse.Data.ID), - fortifyConfig.Spec.Language) - err = fc.BulkUpdate(*bulkUpdateRequest) +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 updating fortify project setting after creation: "+fortifyConfig.Spec.ProjectName) + log.Error(err, "error deleting project from Fortify") 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) + } 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) - } else { - // 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 - } + // 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 } diff --git a/controllers/twistlock/twistlockpipelineconfiguration_controller.go b/controllers/twistlock/twistlockpipelineconfiguration_controller.go index 24e21bd..9692835 100644 --- a/controllers/twistlock/twistlockpipelineconfiguration_controller.go +++ b/controllers/twistlock/twistlockpipelineconfiguration_controller.go @@ -139,18 +139,113 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr // 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 { - log.Error(err, "error updating finalizer list") - return + err = r.handleCreateOrUpdate(ctx, twistlockConfig, log, &tlc) + } else { + err = r.handleDelete(ctx, twistlockConfig, log, &tlc) + } + 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) } + } - //gets all the registry specifications from Twistlock + //if need to update the project, we update it + if needUpdate { + 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) + } else { + //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) 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 { @@ -158,120 +253,34 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr return } - //search to see if spec already exists in Twistlock - specsToUpdate := make([]twistlock.Specification, 0) - var needUpdate bool + newSpecs := make([]twistlock.Specification, 0) + var needToDelete bool for _, spec := range registrySpecs.Specifications { - //if spec already exists, update it + //if spec already exists, then we need to delete/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 + needToDelete = true + continue } else { - specsToUpdate = append(specsToUpdate, spec) + newSpecs = append(newSpecs, spec) } } - //if need to update the project, we update it - if needUpdate { - log.Info("updating project for: " + twistlockConfig.Spec.Repository) - specsRequest := &twistlock.RegistrySpecsUpdateRequest{Specifications: specsToUpdate} + if needToDelete { + specsRequest := &twistlock.RegistrySpecsUpdateRequest{Specifications: newSpecs} 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) - } else { - //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") + log.Error(err, "error updating registries for Twistlock") return } - log.Info("successfully created a new registry spec: " + twistlockConfig.Spec.Repository) } - } 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 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 := make([]twistlock.Specification, 0) - var needToDelete bool - 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) - } - } - - 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 - } + // 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 } -- GitLab From e9fa0e075b99c2f10277491a88e327b8aeb2f0ef Mon Sep 17 00:00:00 2001 From: project_646_bot Date: Mon, 21 Jun 2021 15:49:58 -1000 Subject: [PATCH 16/16] fix sonarqube issues --- ...fortifypipelineconfiguration_controller.go | 132 ++++++++++-------- ...istlockpipelineconfiguration_controller.go | 123 +++++++++------- 2 files changed, 142 insertions(+), 113 deletions(-) diff --git a/controllers/fortify/fortifypipelineconfiguration_controller.go b/controllers/fortify/fortifypipelineconfiguration_controller.go index a3cfda4..90609b3 100644 --- a/controllers/fortify/fortifypipelineconfiguration_controller.go +++ b/controllers/fortify/fortifypipelineconfiguration_controller.go @@ -121,6 +121,76 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr 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 @@ -153,67 +223,9 @@ func (r *PipelineConfigurationReconciler) handleCreateOrUpdate(ctx context.Conte //if project already exists in Fortify, we update it if existingVersionID != -1 { - 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) + err = r.handleUpdate(ctx, fortifyConfig, log, fc, existingVersionID) } else { - //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) + err = r.handleCreate(ctx, fortifyConfig, log, fc) } return } diff --git a/controllers/twistlock/twistlockpipelineconfiguration_controller.go b/controllers/twistlock/twistlockpipelineconfiguration_controller.go index 9692835..712dfee 100644 --- a/controllers/twistlock/twistlockpipelineconfiguration_controller.go +++ b/controllers/twistlock/twistlockpipelineconfiguration_controller.go @@ -146,6 +146,59 @@ func (r *PipelineConfigurationReconciler) Reconcile(ctx context.Context, req ctr 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, @@ -192,48 +245,9 @@ func (r *PipelineConfigurationReconciler) handleCreateOrUpdate(ctx context.Conte //if need to update the project, we update it if needUpdate { - 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) + err = r.handleUpdate(ctx, twistlockConfig, log, tlc, specsToUpdate) } else { - //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) + err = r.handleCrate(ctx, twistlockConfig, log, tlc) } return } @@ -253,18 +267,7 @@ func (r *PipelineConfigurationReconciler) handleDelete(ctx context.Context, twis return } - newSpecs := make([]twistlock.Specification, 0) - var needToDelete bool - 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) - } - } - + newSpecs, needToDelete := determineEndResult(registrySpecs, twistlockConfig) if needToDelete { specsRequest := &twistlock.RegistrySpecsUpdateRequest{Specifications: newSpecs} err = tlc.UpdateRegistrySpec(specsRequest) @@ -284,6 +287,20 @@ func (r *PipelineConfigurationReconciler) handleDelete(ctx context.Context, twis 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{} -- GitLab