diff --git a/.gitignore b/.gitignore index a5d5d1f33f4a7cf0809a9b8bc18afd6b0f048211..a8871530421887c909e04b8a5d1dea746bfdf868 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ coverage.html *.swp *.swo *~ + +.vscode \ No newline at end of file diff --git a/Makefile b/Makefile index b55cf447a2ff389232a029e27cee5f3c44bee03a..024413957dd05cfb05e42e8ec93d203b3a3c3741 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,9 @@ fmt: ## Run go fmt against code. vet: ## Run go vet against code. go vet ./... +lint: ## Run go lint against code + golint -set_exit_status ./... + ENVTEST_ASSETS_DIR=$(shell pwd)/testbin test: # manifests generate ## Run tests. mkdir -p ${ENVTEST_ASSETS_DIR} diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..948202f62e298a2875b5cfb97818d2ad78aa3c22 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +## Valkyrie Automated CICD Provisioning Tool + +### Golang Frameworks +- rlog + - https://github.com/romana/rlog +- go-gitlab + - https://github.com/xanzy/go-gitlab + - https://docs.gitlab.com/ee/api/api_resources.html + - https://pkg.go.dev/github.com/xanzy/go-gitlab?utm_source=godoc#section-documentation +- http-mock + - https://github.com/jarcoal/httpmock + +### lint fmt vet +- run linter + - `golint -set_exit_status ./... ` +- use make + - `make fmt vet lint` + +### Testing +- run tests locally +``` +make build +make test +``` +- generate coverage report + - run AFTER running `make test` +``` +# AFTER running make test +go tool cover -html=cover.out -o coverage.html +open coverage.html +``` +### Integration testing +- integration tests may require setting environment variables +- integration tests may require availability of service + - examples: gitlab, twistlock, fortify, sonarqube +- place integration test files in the ./integration-tests/ folder +- tag test files with a build tag at the beginning of the file +``` +// +build integration + +package integration +``` +- run integration tests as follows + - `go test -v -tags "integration" ./integration-tests/...` \ No newline at end of file diff --git a/apis/gitlab/v1alpha1/group_types_test.go b/apis/gitlab/v1alpha1/group_types_test.go index 03871c4c34981df6a9c1808adb234c88aa8b6905..bf3d636c9f28893f3d15427ba5ae66363981d675 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/clients/gitlab/client.go b/clients/gitlab/client.go new file mode 100644 index 0000000000000000000000000000000000000000..c254baad24bcb9fc347573040504a1c20b430792 --- /dev/null +++ b/clients/gitlab/client.go @@ -0,0 +1,232 @@ +package gitlab + +// Facade built around go-gitlab +// https://github.com/xanzy/go-gitlab +// https://docs.gitlab.com/ee/api/api_resources.html +// https://pkg.go.dev/github.com/xanzy/go-gitlab?utm_source=godoc#section-documentation + +import ( + "fmt" + "github.com/romana/rlog" + gitlab "github.com/xanzy/go-gitlab" + "net/http" +) + +const itemsPerPage = 50 + +// Client - +type Client struct { + client *gitlab.Client + token string + apiURL string +} + +// NewClient - create new gitlab api client +func NewClient(apiURL string, token string, httpClient *http.Client) Client { + // apiUrl https://code.il2.dso.mil/api/v4 + // token gitlab user access token + // httpClient optional injection of http client object to be used by gitlab api + + var gitlabClient *gitlab.Client + if httpClient == nil { + gitlabClient, _ = gitlab.NewClient(token, gitlab.WithBaseURL(apiURL)) + } else { + gitlabClient, _ = gitlab.NewClient(token, gitlab.WithBaseURL(apiURL), gitlab.WithHTTPClient(httpClient)) + } + + rlog.Debug("Created new Client instance") + return Client{ + client: gitlabClient, + token: token, + apiURL: apiURL, + } +} + +// GetUsers - +func (r Client) GetUsers(search *string) ([]*gitlab.User, error) { + listOptions := gitlab.ListOptions{ + Page: 1, + PerPage: itemsPerPage, + } + + // some valid values path, name + var orderBy = "name" + var opts = gitlab.ListUsersOptions{ + ListOptions: listOptions, + OrderBy: &orderBy, + } + // if search defined add it to opts + + if search != nil { + opts.Search = search + } + + userList := []*gitlab.User{} + var more = true + for more { + users, res, err := r.client.Users.ListUsers(&opts) + if err != nil { + return nil, err + } + for x := 0; x < len(users); x++ { + userList = append(userList, users[x]) + } + + if res.NextPage > 0 { + opts.ListOptions.Page = opts.ListOptions.Page + 1 + } else { + more = false + } + + } + rlog.Debug(fmt.Sprintf("Completed call to GetUsers. Records returned: %d", len(userList))) + return userList, nil +} + +// AddUser - +func (r Client) AddUser(user *gitlab.User, password string) (*gitlab.User, int, error) { + var opts = gitlab.CreateUserOptions{ + Username: &user.Username, + Email: &user.Email, + Name: &user.Name, + Password: &password, + } + newUser, res, err := r.client.Users.CreateUser(&opts) + if err != nil { + return nil, res.StatusCode, err + } + rlog.Debug(fmt.Sprintf("Completed call to AddUser. Status Code: %d", res.StatusCode)) + return newUser, res.StatusCode, nil +} + +// DeleteUser - +func (r Client) DeleteUser(username string) (int, error) { + var opts = gitlab.ListUsersOptions{Username: &username} + users, lres, err := r.client.Users.ListUsers(&opts) + if err != nil { + return lres.StatusCode, err + } + res, err := r.client.Users.DeleteUser(users[0].ID) + if err != nil { + return res.StatusCode, err + } + rlog.Debug(fmt.Sprintf("Completed call to DeleteUser. Status Code: %d", res.StatusCode)) + return res.StatusCode, nil +} + +// GetGroups - +func (r Client) GetGroups(search *string) ([]*gitlab.Group, error) { + listOptions := gitlab.ListOptions{ + Page: 1, + PerPage: itemsPerPage, + } + + // some valid values path, name + var orderBy = "path" + var opts = gitlab.ListGroupsOptions{ + ListOptions: listOptions, + OrderBy: &orderBy, + } + // if search defined add it to opts + + if search != nil { + opts.Search = search + } + groupList := []*gitlab.Group{} + var more = true + for more { + groups, res, err := r.client.Groups.ListGroups(&opts) + if err != nil { + return nil, err + } + for x := 0; x < len(groups); x++ { + groupList = append(groupList, groups[x]) + } + + if res.NextPage > 0 { + opts.ListOptions.Page = opts.ListOptions.Page + 1 + } else { + more = false + } + + } + rlog.Debug(fmt.Sprintf("Completed call to GetGroups. Records returned: %d", len(groupList))) + return groupList, nil +} + +// AddGroup - +func (r Client) AddGroup(group *gitlab.Group) (*gitlab.Group, int, error) { + // force visibility to private + var visibility = gitlab.PrivateVisibility + var opts = gitlab.CreateGroupOptions{Name: &group.Name, + Path: &group.Path, Description: &group.Description, + Visibility: &visibility} + + newGroup, res, err := r.client.Groups.CreateGroup(&opts) + if err != nil { + return nil, res.StatusCode, err + } + return newGroup, res.StatusCode, nil +} + +// DeleteGroup - +func (r Client) DeleteGroup(groupID int) (int, error) { + res, err := r.client.Groups.DeleteGroup(groupID) + if err != nil { + return res.StatusCode, err + } + return res.StatusCode, nil +} + +// GetProject - +func (r Client) GetProject(projectID int) (*gitlab.Project, error) { + opts := gitlab.GetProjectOptions{} + project, res, err := r.client.Projects.GetProject(projectID, &opts) + if err != nil { + return nil, err + } + + rlog.Debug(fmt.Sprintf("Completed call to GetProject. status Code: %d", res.StatusCode)) + return project, nil +} + +// GetProjects - +func (r Client) GetProjects(search *string) ([]*gitlab.Project, error) { + listOptions := gitlab.ListOptions{ + Page: 1, + PerPage: itemsPerPage, + } + + // some valid values path, name + var orderBy = "path" + var opts = gitlab.ListProjectsOptions{ + ListOptions: listOptions, + OrderBy: &orderBy, + } + // if search defined add it to opts + + if search != nil { + opts.Search = search + } + projectList := []*gitlab.Project{} + var more = true + for more { + opts := gitlab.ListProjectsOptions{} + projects, res, err := r.client.Projects.ListProjects(&opts) + if err != nil { + return nil, err + } + for x := 0; x < len(projects); x++ { + projectList = append(projectList, projects[x]) + } + + if res.NextPage > 0 { + opts.ListOptions.Page = opts.ListOptions.Page + 1 + } else { + more = false + } + + } + rlog.Debug(fmt.Sprintf("Completed call to GetProjectss. Records returned: %d", len(projectList))) + return projectList, nil +} diff --git a/clients/gitlab/client_test.go b/clients/gitlab/client_test.go new file mode 100644 index 0000000000000000000000000000000000000000..5185c4973aec571e52ad15a382392bd0c3d68523 --- /dev/null +++ b/clients/gitlab/client_test.go @@ -0,0 +1,551 @@ +package gitlab + +import ( + "net/http" + "reflect" + "testing" + + "github.com/jarcoal/httpmock" + gogitlab "github.com/xanzy/go-gitlab" +) + +func TestNewClient(t *testing.T) { + + testToken := "abcdef" + testURL := "https://test" + testWantURL := "https://test/api/v4/" + testHTTPClient := &http.Client{} + + type args struct { + apiURL string + token string + httpClient *http.Client + } + tests := []struct { + name string + args args + wantBaseURL string + }{ + { + name: "New Client", + args: args{testURL, testToken, nil}, + wantBaseURL: testWantURL, + }, + { + name: "New Client with httpClient injection", + args: args{testURL, testToken, testHTTPClient}, + wantBaseURL: testWantURL, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := NewClient(tt.args.apiURL, tt.args.token, tt.args.httpClient) + t.Logf("baseUrl %s", got.client.BaseURL()) + if got.client.BaseURL().String() != tt.wantBaseURL { + t.Errorf("NewClient() = %v, want %v", got.client.BaseURL().String(), tt.wantBaseURL) + } + }) + } +} + +func TestClient_GetUsers(t *testing.T) { + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) + defer httpmock.DeactivateAndReset() + + // empty gitlab User array + testUserArray := []*gogitlab.User{} + testUser1 := gogitlab.User{ID: 1, Username: "joedirt"} + testUser2 := gogitlab.User{ID: 2, Username: "spongebob"} + testUserArray = append(testUserArray, &testUser1, &testUser2) + + // use regex url matcher + httpmock.RegisterResponder("GET", + `=~^https://test/api/v4/users.*`, + httpmock.NewJsonResponderOrPanic(200, testUserArray), + ) + + // test objects + search := "searchstring" + testAPIUrl := "https://test/api/v4/" + testToken := "token" + // create a gitlab Client object, inject http client to allow for mocking using httpmock + testGitlabClient, _ := gogitlab.NewClient(testToken, gogitlab.WithBaseURL(testAPIUrl), gogitlab.WithHTTPClient(testHTTPClient)) + + type fields struct { + client *gogitlab.Client + token string + apiURL string + } + type args struct { + search *string + } + tests := []struct { + name string + fields fields + args args + want []*gogitlab.User + wantErr bool + }{ + { + name: "GetUsers 1", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{search: nil}, + want: testUserArray, + wantErr: false, + }, + { + name: "GetUsers 2", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{search: &search}, + want: testUserArray, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := Client{ + client: tt.fields.client, + token: tt.fields.token, + apiURL: tt.fields.apiURL, + } + + got, err := r.GetUsers(tt.args.search) + if (err != nil) != tt.wantErr { + t.Errorf("Client.GetUsers() error = %v, wantErr %v", err, tt.wantErr) + return + } + for _, user := range got { + t.Logf("user id %d, username %s", user.ID, user.Username) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Client.GetUsers() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestClient_AddUser(t *testing.T) { + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) + defer httpmock.DeactivateAndReset() + + testUser1 := gogitlab.User{ID: 1, Username: "joedirt"} + + httpmock.RegisterResponder("POST", + "https://test/api/v4/users", + httpmock.NewJsonResponderOrPanic(200, testUser1), + ) + + // test objects + testAPIUrl := "https://test/api/v4/" + testToken := "token" + // create a gitlab Client object, inject http client to allow for mocking using httpmock + testGitlabClient, _ := gogitlab.NewClient(testToken, gogitlab.WithBaseURL(testAPIUrl), gogitlab.WithHTTPClient(testHTTPClient)) + + type fields struct { + client *gogitlab.Client + token string + apiURL string + } + type args struct { + user *gogitlab.User + password string + } + tests := []struct { + name string + fields fields + args args + want *gogitlab.User + want1 int + wantErr bool + }{ + { + name: "AddUser 1", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{user: &testUser1, password: "test"}, + want: &testUser1, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := Client{ + client: tt.fields.client, + token: tt.fields.token, + apiURL: tt.fields.apiURL, + } + got, _, err := r.AddUser(tt.args.user, tt.args.password) + if (err != nil) != tt.wantErr { + t.Errorf("Client.AddUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got.ID != tt.want.ID { + t.Errorf("Client.AddUser() got = %d, want %d", got.ID, tt.want.ID) + return + } + t.Logf("Client.AddUser() got = %d, want %d", got.ID, tt.want.ID) + }) + } +} + +func TestClient_DeleteUser(t *testing.T) { + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) + defer httpmock.DeactivateAndReset() + + testUser := gogitlab.User{ID: 1, Username: "joedirt"} + testUserArray := []*gogitlab.User{} + testUserArray = append(testUserArray, &testUser) + + httpmock.RegisterResponder("DELETE", + `=~^https://test/api/v4/users.*`, + httpmock.NewJsonResponderOrPanic(202, testUser), + ) + httpmock.RegisterResponder("GET", + `=~^https://test/api/v4/users.*`, + httpmock.NewJsonResponderOrPanic(200, testUserArray), + ) + + // test objects + testUsername := "testusername" + testAPIUrl := "https://test/api/v4/" + testToken := "token" + // create a gitlab Client object, inject http client to allow for mocking using httpmock + testGitlabClient, _ := gogitlab.NewClient(testToken, gogitlab.WithBaseURL(testAPIUrl), gogitlab.WithHTTPClient(testHTTPClient)) + + type fields struct { + client *gogitlab.Client + token string + apiURL string + } + type args struct { + username string + } + tests := []struct { + name string + fields fields + args args + want int + wantErr bool + }{ + { + name: "DeleteUser 1", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{username: testUsername}, + want: 202, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := Client{ + client: tt.fields.client, + token: tt.fields.token, + apiURL: tt.fields.apiURL, + } + got, err := r.DeleteUser(tt.args.username) + if (err != nil) != tt.wantErr { + t.Errorf("Client.DeleteUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("Client.DeleteUser() = %v, want %v", got, tt.want) + return + } + t.Logf("Client.DeleteUser() statusCode = %d ", got) + }) + } +} + +func TestClient_GetGroups(t *testing.T) { + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) + defer httpmock.DeactivateAndReset() + + // empty gitlab User array + testGroupArray := []*gogitlab.Group{} + testGroup1 := gogitlab.Group{ID: 1, Name: "joedirt"} + testGroup2 := gogitlab.Group{ID: 2, Name: "spongebob"} + testGroupArray = append(testGroupArray, &testGroup1, &testGroup2) + + // use regex url matcher + httpmock.RegisterResponder("GET", + `=~^https://test/api/v4/groups.*`, + httpmock.NewJsonResponderOrPanic(200, testGroupArray), + ) + + // test objects + search := "searchstring" + testAPIUrl := "https://test/api/v4/" + testToken := "token" + // create a gitlab Client object, inject http client to allow for mocking using httpmock + testGitlabClient, _ := gogitlab.NewClient(testToken, gogitlab.WithBaseURL(testAPIUrl), gogitlab.WithHTTPClient(testHTTPClient)) + + type fields struct { + client *gogitlab.Client + token string + apiURL string + } + type args struct { + search *string + } + tests := []struct { + name string + fields fields + args args + want []*gogitlab.Group + wantErr bool + }{ + { + name: "GetGroups 1", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{search: nil}, + want: testGroupArray, + wantErr: false, + }, + { + name: "GetGroups 2", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{search: &search}, + want: testGroupArray, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := Client{ + client: tt.fields.client, + token: tt.fields.token, + apiURL: tt.fields.apiURL, + } + + got, err := r.GetGroups(tt.args.search) + if (err != nil) != tt.wantErr { + t.Errorf("Client.GetGroups() error = %v, wantErr %v", err, tt.wantErr) + return + } + for _, group := range got { + t.Logf("user id %d, groupname %s", group.ID, group.Name) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Client.GetGroups() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestClient_AddGroup(t *testing.T) { + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) + defer httpmock.DeactivateAndReset() + + // empty gitlab User array + testGroup := gogitlab.Group{ID: 1, Name: "joedirt"} + + httpmock.RegisterResponder("POST", + "https://test/api/v4/groups", + httpmock.NewJsonResponderOrPanic(200, testGroup), + ) + + // test objects + testAPIUrl := "https://test/api/v4/" + testToken := "token" + // create a gitlab Client object, inject http client to allow for mocking using httpmock + testGitlabClient, _ := gogitlab.NewClient(testToken, gogitlab.WithBaseURL(testAPIUrl), gogitlab.WithHTTPClient(testHTTPClient)) + + type fields struct { + client *gogitlab.Client + token string + apiURL string + } + type args struct { + group *gogitlab.Group + } + tests := []struct { + name string + fields fields + args args + want *gogitlab.Group + want1 int + wantErr bool + }{ + { + name: "AddGroup 1", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{group: &testGroup}, + want: &testGroup, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := Client{ + client: tt.fields.client, + token: tt.fields.token, + apiURL: tt.fields.apiURL, + } + got, _, err := r.AddGroup(tt.args.group) + if (err != nil) != tt.wantErr { + t.Errorf("Client.AddGroup() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got.ID != tt.want.ID { + t.Errorf("Client.AddGroup() got = %d, want %d", got.ID, tt.want.ID) + return + } + t.Logf("Client.AddGroup() got = %d %s, want %d %s", got.ID, got.Name, tt.want.ID, tt.want.Name) + }) + } +} + +func TestClient_DeleteGroup(t *testing.T) { + + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) + defer httpmock.DeactivateAndReset() + // empty gitlab User array + testGroupArray := []*gogitlab.Group{} + testGroup1 := gogitlab.Group{ID: 1, Name: "group joedirt"} + testGroup2 := gogitlab.Group{ID: 2, Name: "group spongebob"} + testGroupArray = append(testGroupArray, &testGroup1, &testGroup2) + + httpmock.RegisterResponder("DELETE", + `=~^https://test/api/v4/groups.*`, + httpmock.NewJsonResponderOrPanic(202, testGroup1), + ) + httpmock.RegisterResponder("GET", + `=~^https://test/api/v4/groups.*`, + httpmock.NewJsonResponderOrPanic(200, testGroupArray), + ) + + // test objects + testGroupID := 1 + testAPIUrl := "https://test/api/v4/" + testToken := "token" + // create a gitlab Client object, inject http client to allow for mocking using httpmock + testGitlabClient, _ := gogitlab.NewClient(testToken, gogitlab.WithBaseURL(testAPIUrl), gogitlab.WithHTTPClient(testHTTPClient)) + + type fields struct { + client *gogitlab.Client + token string + apiURL string + } + type args struct { + groupID int + } + tests := []struct { + name string + fields fields + args args + want int + wantErr bool + }{ + { + name: "DeleteGroup 1", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{groupID: testGroupID}, + want: 202, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := Client{ + client: tt.fields.client, + token: tt.fields.token, + apiURL: tt.fields.apiURL, + } + got, err := r.DeleteGroup(tt.args.groupID) + if (err != nil) != tt.wantErr { + t.Errorf("Client.DeleteGroup() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("Client.รง() = %v, want %v", got, tt.want) + return + } + t.Logf("Client.DeleteGroup() statusCode = %d ", got) + }) + } +} + +// func TestClient_GetProject(t *testing.T) { +// type fields struct { +// client *gitlab.Client +// token string +// apiURL string +// } +// type args struct { +// projectID int +// } +// tests := []struct { +// name string +// fields fields +// args args +// want *gitlab.Project +// wantErr bool +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// r := Client{ +// client: tt.fields.client, +// token: tt.fields.token, +// apiURL: tt.fields.apiURL, +// } +// got, err := r.GetProject(tt.args.projectID) +// if (err != nil) != tt.wantErr { +// t.Errorf("Client.GetProject() error = %v, wantErr %v", err, tt.wantErr) +// return +// } +// if !reflect.DeepEqual(got, tt.want) { +// t.Errorf("Client.GetProject() = %v, want %v", got, tt.want) +// } +// }) +// } +// } + +// func TestClient_GetProjects(t *testing.T) { +// type fields struct { +// client *gitlab.Client +// token string +// apiURL string +// } +// type args struct { +// search *string +// } +// tests := []struct { +// name string +// fields fields +// args args +// want []*gitlab.Project +// wantErr bool +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// r := Client{ +// client: tt.fields.client, +// token: tt.fields.token, +// apiURL: tt.fields.apiURL, +// } +// got, err := r.GetProjects(tt.args.search) +// if (err != nil) != tt.wantErr { +// t.Errorf("Client.GetProjects() error = %v, wantErr %v", err, tt.wantErr) +// return +// } +// if !reflect.DeepEqual(got, tt.want) { +// t.Errorf("Client.GetProjects() = %v, want %v", got, tt.want) +// } +// }) +// } +// } diff --git a/controllers/gitlab/gitlab_client.go b/controllers/gitlab/gitlab_client.go index abc9052a346e665283ef3e63abb58aeb30fa294e..57999a45219a1679388f6482a674d5e3463ecc11 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 db8c208cfed76d2caf87eba338b621074818ea8f..46f433d50486e1d095819eec119f0a0eeef75ba6 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 3dd202447977976ce5207c650b1bbb24ac05935a..c97e08658e42cfa98a8350dfe8d056a157431456 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 ecca40c9d36b295f6b2d2546b9c4c31fde1a9d62..1716f5dd15337006e9907a76d7d0dcd57707e41e 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...) +} diff --git a/go.mod b/go.mod index 57c0834aa0436795d35c9b55671ed58b3b2d6b40..3f509c0d0d6bb38d43179bea8f3de710e066a3ca 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/jinzhu/copier v0.3.2 github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.13.0 + github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7 github.com/xanzy/go-gitlab v0.50.0 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect diff --git a/go.sum b/go.sum index 47ea8a2f47d1435fb74ed2d37e160316db763cca..f0aca23368a0a780adb73faa50bb2e598062fe22 100644 --- a/go.sum +++ b/go.sum @@ -42,10 +42,8 @@ github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdko github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -161,6 +159,7 @@ github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+ github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= @@ -345,6 +344,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7 h1:jkvpcEatpwuMF5O5LVxTnehj6YZ/aEZN4NWD/Xml4pI= +github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7/go.mod h1:KTrHyWpO1sevuXPZwyeZc72ddWRFqNSKDFl7uVWKpg0= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= diff --git a/integration-tests/gitlab/api/gitlab_api_adhoc_test.go b/integration-tests/gitlab/api/gitlab_api_adhoc_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c3f4cca5041e28fecce8ae197588668bafbdb46a --- /dev/null +++ b/integration-tests/gitlab/api/gitlab_api_adhoc_test.go @@ -0,0 +1,139 @@ +// +build integration + +package integration + +import ( + "testing" + "errors" + "os" + gitlab "valkyrie.dso.mil/valkyrie-api/clients/gitlab" +) + +// getClient_AdHoc - +func getClient_AdHoc() (gitlab.Client, error) { + gitlabAPIURL, ok := os.LookupEnv("GITLAB_API_URL") + if !ok { + return gitlab.Client{}, errors.New("env variable GITLAB_API_URL undefinded") + } + gitlabAPIToken, ok := os.LookupEnv("GITLAB_API_TOKEN") + if !ok { + return gitlab.Client{}, errors.New("env variable GITLAB_API_TOKEN undefinded") + } + var client = gitlab.NewClient(gitlabAPIURL, gitlabAPIToken,nil) + return client, nil +} +func TestClient_AdHoc_getUsers(t *testing.T) { + t.Run("test", func(t *testing.T) { + c, err := getClient_AdHoc() + if err != nil { + t.Errorf("Client.GetUsers() error = %v", err) + return + } + got, err := c.GetUsers(nil) + if err != nil { + t.Errorf("Client.GetUsers() error = %v", err) + return + } + t.Logf("GetUsers %d", len(got)) + for x:=0;x