diff --git a/README.md b/README.md index f15ad9d0680969cfe46011dd69d1ea90667ea64c..7d96782227bdb84dcb71fb4a8781a87d9672746e 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,8 @@ package integration ``` - run integration tests as follows - `go test -v -tags "integration" ./integration-tests/...` +- clean test cache + - `go clean -testcache` ### Twistlock Controller Setup 1) Create a secret that holds the credential for Twistlock API diff --git a/clients/gitlab/client.go b/clients/gitlab/client.go index 7b39504e56c0f81068f3749092fa027712f0f074..21374d626fd749ced042da8c2ca7c82210b380fd 100644 --- a/clients/gitlab/client.go +++ b/clients/gitlab/client.go @@ -446,7 +446,7 @@ func (r Client) AddGroupMember(groupID *int, userID *int, accessLevel gogitlab.A AccessLevel: &accessLevel, } - groupMember, res, err := r.client.GroupMembers.AddGroupMember(groupID, &opts) + groupMember, res, err := r.client.GroupMembers.AddGroupMember(*groupID, &opts) if err != nil { processError(logPrefix, err) return nil, 0, err @@ -635,10 +635,11 @@ func (r Client) AddProject(createProjectOptions gogitlab.CreateProjectOptions) ( var visibility = gogitlab.PrivateVisibility // copy customizable settings from argument, hard code other options as desired var opts = gogitlab.CreateProjectOptions{ - Name: createProjectOptions.Name, - Path: createProjectOptions.Path, - Description: createProjectOptions.Description, - NamespaceID: createProjectOptions.NamespaceID, + Name: createProjectOptions.Name, + Path: createProjectOptions.Path, + Description: createProjectOptions.Description, + CIConfigPath: createProjectOptions.CIConfigPath, + NamespaceID: createProjectOptions.NamespaceID, // hard coded values Visibility: &visibility, } @@ -659,9 +660,10 @@ func (r Client) UpdateProject(projectID int, editProjectOptions gogitlab.EditPro logPrefix := "UpdateProject" // copy customizable settings from argument, hard code other options as desired var opts = gogitlab.EditProjectOptions{ - Name: editProjectOptions.Name, - Path: editProjectOptions.Path, - Description: editProjectOptions.Description, + Name: editProjectOptions.Name, + Path: editProjectOptions.Path, + Description: editProjectOptions.Description, + CIConfigPath: editProjectOptions.CIConfigPath, } project, res, err := r.client.Projects.EditProject(projectID, &opts) diff --git a/clients/gitlab/client_test.go b/clients/gitlab/client_test.go index 9073ddc5ea7a86b1096d12281566431a2771e246..ecde48da07c463d85689c67e398980c2197c1d65 100644 --- a/clients/gitlab/client_test.go +++ b/clients/gitlab/client_test.go @@ -116,6 +116,94 @@ func TestClient_GetUser(t *testing.T) { } } +func TestClient_GetUserByUsername(t *testing.T) { + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) + defer httpmock.DeactivateAndReset() + + testUserID := 1 + testUsername := "testusername" + testUser := gogitlab.User{ID: testUserID, Username: testUsername} + testUserArray := []*gogitlab.User{} + testUserArray = append(testUserArray, &testUser) + testUserArrayEmpty := []*gogitlab.User{} + + // 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 { + username *string + } + tests := []struct { + name string + fields fields + args args + want *gogitlab.User + want1 int + wantErr bool + }{ + { + name: "GetUserBuUsername 1", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{username: &testUsername}, + want: &testUser, + want1: http.StatusFound, + wantErr: false, + }, + { + name: "GetUserBuUsername 2", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{username: &testUsername}, + want: nil, + want1: http.StatusNotFound, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // default mock responder + httpmock.RegisterResponder("GET", + `=~^https://test/api/v4/users.*`, + httpmock.NewJsonResponderOrPanic(http.StatusOK, testUserArray), + ) + + // if we want to test no results found use this responder + if tt.want1 == http.StatusNotFound { + httpmock.RegisterResponder("GET", + `=~^https://test/api/v4/users.*`, + httpmock.NewJsonResponderOrPanic(http.StatusOK, testUserArrayEmpty), + ) + } + + r := Client{ + client: tt.fields.client, + token: tt.fields.token, + apiURL: tt.fields.apiURL, + } + got, got1, err := r.GetUserByUsername(tt.args.username) + if (err != nil) != tt.wantErr { + t.Errorf("Client.GetUserByUsername() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Client.GetUserByUsername() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("Client.GetUserByUsername() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + func TestClient_GetUsers(t *testing.T) { // setup a http client for use in mocking testHTTPClient := &http.Client{} @@ -384,7 +472,9 @@ func TestClient_DeleteUser(t *testing.T) { apiURL string } type args struct { - userID int + userID int + waitInterval int + waitCount int } tests := []struct { name string @@ -394,10 +484,17 @@ func TestClient_DeleteUser(t *testing.T) { wantErr bool }{ { - name: "DeleteUser 1", + name: "DeleteUser Success", fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, - args: args{userID: testUserID}, - want: 200, + args: args{userID: testUserID, waitInterval: 1000, waitCount: 10}, + want: http.StatusOK, + wantErr: false, + }, + { + name: "DeleteUser Timeout", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{userID: testUserID, waitInterval: 1000, waitCount: 1}, + want: http.StatusRequestTimeout, wantErr: false, }, } @@ -408,7 +505,7 @@ func TestClient_DeleteUser(t *testing.T) { token: tt.fields.token, apiURL: tt.fields.apiURL, } - got, err := r.DeleteUser(tt.args.userID, 1000, 10) + got, err := r.DeleteUser(tt.args.userID, tt.args.waitInterval, tt.args.waitCount) if (err != nil) != tt.wantErr { t.Errorf("Client.DeleteUser() error = %v, wantErr %v", err, tt.wantErr) return @@ -431,6 +528,7 @@ func TestClient_DeleteUserByUsername(t *testing.T) { testUser := gogitlab.User{ID: 1, Username: "joedirt"} testUserArray := []*gogitlab.User{} testUserArray = append(testUserArray, &testUser) + testUserArrayEmpty := []*gogitlab.User{} counter := 0 httpmock.RegisterResponder("DELETE", @@ -440,18 +538,6 @@ func TestClient_DeleteUserByUsername(t *testing.T) { }, ) - // setup a mock that will change response on every 4th call - httpmock.RegisterResponder("GET", - `=~^https://test/api/v4/users.*`, - func(req *http.Request) (*http.Response, error) { - counter = counter + 1 - if counter%4 == 0 { - return httpmock.NewJsonResponse(404, testUserArray) - } - return httpmock.NewJsonResponse(202, testUserArray) - }, - ) - // test objects testUsername := "testusername" testAPIUrl := "https://test/api/v4/" @@ -475,14 +561,39 @@ func TestClient_DeleteUserByUsername(t *testing.T) { wantErr bool }{ { - name: "DeleteUserByUsername 1", + name: "DeleteUserByUsername Success", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{username: testUsername}, + want: http.StatusOK, + wantErr: false, + }, + { + name: "DeleteUserByUsername Not Found", fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, args: args{username: testUsername}, - want: 200, + want: http.StatusNotFound, wantErr: false, }, } for _, tt := range tests { + // default responder + // setup a mock that will change response on every 4th call + httpmock.RegisterResponder("GET", + `=~^https://test/api/v4/users.*`, + func(req *http.Request) (*http.Response, error) { + counter = counter + 1 + if counter%4 == 0 { + return httpmock.NewJsonResponse(404, testUserArray) + } + return httpmock.NewJsonResponse(202, testUserArray) + }, + ) + if tt.want == http.StatusNotFound { + httpmock.RegisterResponder("GET", + `=~^https://test/api/v4/users.*`, + httpmock.NewJsonResponderOrPanic(http.StatusOK, testUserArrayEmpty), + ) + } t.Run(tt.name, func(t *testing.T) { r := Client{ client: tt.fields.client, @@ -570,6 +681,81 @@ func TestClient_GetGroup(t *testing.T) { }) } } + +func TestClient_GetGroupByFullPath(t *testing.T) { + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) + defer httpmock.DeactivateAndReset() + + // empty gitlab User array + testFullPath1 := "group1/group2" + testFullPath2 := "group1/group3" + testGroupArray := []*gogitlab.Group{} + testGroup1 := gogitlab.Group{ID: 1, Name: "joedirt", FullPath: testFullPath1} + testGroup2 := gogitlab.Group{ID: 2, Name: "spongebob", FullName: testFullPath2} + testGroupArray = append(testGroupArray, &testGroup1, &testGroup2) + + // use regex url matcher + httpmock.RegisterResponder("GET", + `=~^https://test/api/v4/groups.*`, + httpmock.NewJsonResponderOrPanic(http.StatusOK, testGroupArray), + ) + + // 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 { + fullPath *string + } + tests := []struct { + name string + fields fields + args args + want *gogitlab.Group + want1 int + wantErr bool + }{ + { + name: "GetGroups 1", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{fullPath: &testFullPath1}, + want: &testGroup1, + want1: http.StatusFound, + 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, got1, err := r.GetGroupByFullPath(tt.args.fullPath) + if (err != nil) != tt.wantErr { + t.Errorf("Client.GetGroupByFullPath() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Client.GetGroupByFullPath() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("Client.GetGroupByFullPath() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + func TestClient_GetGroups(t *testing.T) { // setup a http client for use in mocking testHTTPClient := &http.Client{} @@ -714,6 +900,150 @@ func TestClient_AddGroup(t *testing.T) { } } +func TestClient_AddGroupMember(t *testing.T) { + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) + defer httpmock.DeactivateAndReset() + + // empty gitlab User array + testGroupID := 1 + testUserID := 1 + // testGroupName := "test group name" + // testGroup := gogitlab.Group{ID: testGroupID, Name: testGroupName} + testAccessLevel := gogitlab.OwnerPermissions + testGroupMember := gogitlab.GroupMember{ID: testUserID} + + httpmock.RegisterResponder("POST", + `=~^https://test/api/v4/groups.*`, + httpmock.NewJsonResponderOrPanic(200, testGroupMember), + ) + + // 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 { + groupID *int + userID *int + accessLevel gogitlab.AccessLevelValue + } + tests := []struct { + name string + fields fields + args args + want *gogitlab.GroupMember + want1 int + wantErr bool + }{ + { + name: "AddGroupMember 1", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{groupID: &testGroupID, userID: &testUserID, accessLevel: testAccessLevel}, + want: &testGroupMember, + want1: http.StatusOK, + 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, got1, err := r.AddGroupMember(tt.args.groupID, tt.args.userID, tt.args.accessLevel) + if (err != nil) != tt.wantErr { + t.Errorf("Client.AddGroupMember() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Client.AddGroupMember() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("Client.AddGroupMember() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func TestClient_UpdateGroup(t *testing.T) { + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) + defer httpmock.DeactivateAndReset() + + testGroupID := 1 + testUpdatedName := "test group UPDATED name" + testUpdatedGroup := gogitlab.Group{ID: testGroupID, Name: testUpdatedName} + + httpmock.RegisterResponder("PUT", + `=~^https://test/api/v4/groups.*`, + httpmock.NewJsonResponderOrPanic(http.StatusOK, testUpdatedGroup), + ) + + // 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 { + groupID int + updateGroupOptions *gogitlab.UpdateGroupOptions + } + tests := []struct { + name string + fields fields + args args + want *gogitlab.Group + want1 int + wantErr bool + }{ + { + name: "UpdateGroup 1", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{groupID: testGroupID, updateGroupOptions: &gogitlab.UpdateGroupOptions{Name: &testUpdatedName}}, + want: &testUpdatedGroup, + want1: http.StatusOK, + 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, got1, err := r.UpdateGroup(tt.args.groupID, tt.args.updateGroupOptions) + if (err != nil) != tt.wantErr { + t.Errorf("Client.UpdateGroup() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Client.UpdateGroup() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("Client.UpdateGroup() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + func TestClient_DeleteGroup(t *testing.T) { // setup a http client for use in mocking @@ -752,7 +1082,9 @@ func TestClient_DeleteGroup(t *testing.T) { apiURL string } type args struct { - groupID int + groupID int + waitInterval int + waitCount int } tests := []struct { name string @@ -762,10 +1094,17 @@ func TestClient_DeleteGroup(t *testing.T) { wantErr bool }{ { - name: "DeleteGroup 1", + name: "DeleteGroup Success", fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, - args: args{groupID: testGroupID}, - want: 200, + args: args{groupID: testGroupID, waitInterval: 1000, waitCount: 10}, + want: http.StatusOK, + wantErr: false, + }, + { + name: "DeleteGroup Timeout", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{groupID: testGroupID, waitInterval: 1000, waitCount: 1}, + want: http.StatusRequestTimeout, wantErr: false, }, } @@ -776,7 +1115,7 @@ func TestClient_DeleteGroup(t *testing.T) { token: tt.fields.token, apiURL: tt.fields.apiURL, } - got, err := r.DeleteGroup(tt.args.groupID, 1000, 10) + got, err := r.DeleteGroup(tt.args.groupID, tt.args.waitInterval, tt.args.waitCount) if (err != nil) != tt.wantErr { t.Errorf("Client.DeleteGroup() error = %v, wantErr %v", err, tt.wantErr) return @@ -929,6 +1268,78 @@ func TestClient_GetProjects(t *testing.T) { } } +func TestClient_GetProjectByFullPath(t *testing.T) { + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) + defer httpmock.DeactivateAndReset() + + // empty gitlab User array + testProjectArray := []*gogitlab.Project{} + testProjectFullPath1 := "root/group1/group2/project1" + testProjectFullPath2 := "root/group1/project2" + testProject1 := gogitlab.Project{ID: 1, Name: "joedirt", PathWithNamespace: testProjectFullPath1} + testProject2 := gogitlab.Project{ID: 2, Name: "spongebob", PathWithNamespace: testProjectFullPath2} + testProjectArray = append(testProjectArray, &testProject1, &testProject2) + + // use regex url matcher + httpmock.RegisterResponder("GET", + `=~^https://test/api/v4/projects.*`, + httpmock.NewJsonResponderOrPanic(http.StatusOK, testProjectArray), + ) + // 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 { + fullPath *string + } + tests := []struct { + name string + fields fields + args args + want *gogitlab.Project + want1 int + wantErr bool + }{ + { + name: "GetProjects 1", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{fullPath: &testProjectFullPath1}, + want: &testProject1, + want1: http.StatusFound, + 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, got1, err := r.GetProjectByFullPath(tt.args.fullPath) + if (err != nil) != tt.wantErr { + t.Errorf("Client.GetProjectByFullPath() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Client.GetProjectByFullPath() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("Client.GetProjectByFullPath() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + func TestClient_AddProject(t *testing.T) { // setup a http client for use in mocking @@ -999,3 +1410,158 @@ func TestClient_AddProject(t *testing.T) { }) } } + +func TestClient_UpdateProject(t *testing.T) { + + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) + defer httpmock.DeactivateAndReset() + + testProjectID := 1 + testUpdatedProjectName := "testProjectUPDATEDName" + testUpdatedProject := gogitlab.Project{ID: testProjectID, Name: testUpdatedProjectName} + + httpmock.RegisterResponder("PUT", + `=~^https://test/api/v4/project.*`, + func(req *http.Request) (*http.Response, error) { + return httpmock.NewJsonResponse(http.StatusOK, testUpdatedProject) + }, + ) + + // 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 { + projectID int + editProjectOptions gogitlab.EditProjectOptions + } + tests := []struct { + name string + fields fields + args args + want *gogitlab.Project + want1 int + wantErr bool + }{ + { + name: "UpdateProject Success", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{projectID: testProjectID}, + want: &testUpdatedProject, + want1: http.StatusOK, + 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, got1, err := r.UpdateProject(tt.args.projectID, tt.args.editProjectOptions) + if (err != nil) != tt.wantErr { + t.Errorf("Client.UpdateProject() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Client.UpdateProject() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("Client.UpdateProject() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func TestClient_DeleteProject(t *testing.T) { + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) + defer httpmock.DeactivateAndReset() + + testProjectID := 1 + testProjectName := "testProjectName" + testProject1 := gogitlab.Project{ID: testProjectID, Name: testProjectName} + + var counter = 0 + httpmock.RegisterResponder("DELETE", + `=~^https://test/api/v4/project.*`, + func(req *http.Request) (*http.Response, error) { + return httpmock.NewJsonResponse(201, testProject1) + }, + ) + httpmock.RegisterResponder("GET", + `=~^https://test/api/v4/projects.*`, + func(req *http.Request) (*http.Response, error) { + counter = counter + 1 + if counter%4 == 0 { + return httpmock.NewJsonResponse(404, testProject1) + } + return httpmock.NewJsonResponse(202, testProject1) + }) + // 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 { + projectID int + waitInterval int + waitCount int + } + tests := []struct { + name string + fields fields + args args + want int + wantErr bool + }{ + { + name: "DeleteProject Success", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{projectID: testProjectID, waitInterval: 1000, waitCount: 10}, + want: http.StatusOK, + wantErr: false, + }, + { + name: "DeleteProject Timeout", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{projectID: testProjectID, waitInterval: 1000, waitCount: 1}, + want: http.StatusRequestTimeout, + 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.DeleteProject(tt.args.projectID, tt.args.waitInterval, tt.args.waitCount) + if (err != nil) != tt.wantErr { + t.Errorf("Client.DeleteProject() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("Client.DeleteProject() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/custom/p1/project.go b/custom/p1/project.go new file mode 100644 index 0000000000000000000000000000000000000000..c4c6064fbfad5f98a7160b46621ab7c1955ff4f8 --- /dev/null +++ b/custom/p1/project.go @@ -0,0 +1,153 @@ +package p1 + +import ( + "fmt" + "net/http" + + "github.com/romana/rlog" + gogitlab "github.com/xanzy/go-gitlab" + apigitlab "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" + gitlab "valkyrie.dso.mil/valkyrie-api/clients/gitlab" +) + +// processError - helper +func processError(logPrefix string, err error) { + rlog.Warnf("%s error: %v", logPrefix, err) +} + +func logStart(logPrefix string, projectSpec apigitlab.ProjectSpec) { + rlog.Debugf("%s start. projectConfig %s %s %s", + logPrefix, + projectSpec.Name, + projectSpec.GroupID, + projectSpec.Language) +} + +// CreateProject - +func CreateProject(projectCredentials ProjectCredentials, projectSpec apigitlab.ProjectSpec, httpClient *http.Client) error { + logPrefix := "CreateProject" + logStart(logPrefix, projectSpec) + + client := gitlab.NewClient(projectCredentials.ServerURL, projectCredentials.ServerToken, httpClient) + + // confirm parent group by ID + group, statusCode, err := client.GetGroup(int(projectSpec.GroupID)) + if err != nil { + return fmt.Errorf(fmt.Sprintf("Failed to find group with ID %d. error: %v", int(projectSpec.GroupID), err)) + } + if statusCode != http.StatusOK { + return fmt.Errorf(fmt.Sprintf("Failed to find group with ID %d. status code: %d", int(projectSpec.GroupID), statusCode)) + } + + // confirm parent group full path + parentGroupFullPath := group.FullPath + parentGroup, statusCode, err := client.GetGroupByFullPath(&parentGroupFullPath) + if err != nil { + return fmt.Errorf(fmt.Sprintf("Failed to find group with ID %d. error: %v", int(projectSpec.GroupID), err)) + } + if statusCode != http.StatusFound { + return fmt.Errorf(fmt.Sprintf("Failed to find group with ID %d. status code: %d", int(projectSpec.GroupID), statusCode)) + } + + ciConfigPath := generateCIConfigPath(projectSpec.Name, parentGroupFullPath) + projectOptions := gogitlab.CreateProjectOptions{ + Name: &projectSpec.Name, + NamespaceID: &parentGroup.ID, + CIConfigPath: &ciConfigPath, + } + + // TODO: add or check branch with group name to pipeline-products + // TODO: add initial product yaml file on that branch based on language type + // TODO: add MR to pipeline product to create project-ci.yml file that POINTS to the branch + + project, statusCode, err := client.AddProject(projectOptions) + if err != nil { + return fmt.Errorf(fmt.Sprintf("Failed to add project with name %s to group path %s. error: %v", *projectOptions.Name, parentGroupFullPath, err)) + } + if statusCode != http.StatusCreated { + return fmt.Errorf(fmt.Sprintf("Failed to add project with name %s to group path %s. statusCode: %d", *projectOptions.Name, parentGroupFullPath, statusCode)) + + } + rlog.Debugf("Added project %s to path %s with status code %d", project.Name, project.NameWithNamespace, statusCode) + + return nil +} + +// DeleteProject - +func DeleteProject(projectCredentials ProjectCredentials, projectSpec apigitlab.ProjectSpec, httpClient *http.Client) error { + logPrefix := "DeleteProject" + logStart(logPrefix, projectSpec) + + client := gitlab.NewClient(projectCredentials.ServerURL, projectCredentials.ServerToken, httpClient) + group, _, err := client.GetGroup(int(projectSpec.GroupID)) + if err != nil { + return fmt.Errorf(fmt.Sprintf("Faild to find group with ID %d", int(projectSpec.GroupID))) + } + parentGroupFullPath := group.FullPath + // get scrubbed pathname based on actual name + projectPathString, _ := GenerateGitlabPath(projectSpec.Name) + projectFullPath := parentGroupFullPath + "/" + projectPathString + project, statusCode, err := client.GetProjectByFullPath(&projectFullPath) + if err != nil { + processError(logPrefix, err) + return err + } + if statusCode != http.StatusFound { + return fmt.Errorf(fmt.Sprintf("Failed to find project with full path %s", projectFullPath)) + } + + _, err = client.DeleteProject(project.ID, 0, 0) + + if err != nil { + return fmt.Errorf(fmt.Sprintf("Faild to delete project with ID %d", int(project.ID))) + } + return nil +} + +// UpdateProject - +func UpdateProject(projectCredentials ProjectCredentials, projectSpec apigitlab.ProjectSpec, httpClient *http.Client) error { + logPrefix := "UpdateProject" + logStart(logPrefix, projectSpec) + + client := gitlab.NewClient(projectCredentials.ServerURL, projectCredentials.ServerToken, httpClient) + + // Identify parent Group for project + group, _, err := client.GetGroup(int(projectSpec.GroupID)) + if err != nil { + processError(logPrefix, err) + return fmt.Errorf(fmt.Sprintf("Faild to find group with ID %d", int(projectSpec.GroupID))) + } + parentGroupFullPath := group.FullPath + // get scrubbed pathname based on actual name + projectPathString, _ := GenerateGitlabPath(projectSpec.Name) + projectFullPath := parentGroupFullPath + "/" + projectPathString + project, statusCode, err := client.GetProjectByFullPath(&projectFullPath) + if err != nil { + processError(logPrefix, err) + return err + } + if statusCode != http.StatusFound { + return fmt.Errorf(fmt.Sprintf("Failed to find project with full path %s", projectFullPath)) + } + + ciConfigPath := generateCIConfigPath(projectSpec.Name, parentGroupFullPath) + projectOptions := gogitlab.EditProjectOptions{ + Name: &projectSpec.Name, + CIConfigPath: &ciConfigPath, + } + + project, statusCode, err = client.UpdateProject(project.ID, projectOptions) + + if err != nil { + processError(logPrefix, err) + return fmt.Errorf(fmt.Sprintf("Faild to update project with ID %d error: %v", int(project.ID), err)) + } + rlog.Debugf("Updated project %s with status code %d", project.Name, statusCode) + return nil +} + +func generateCIConfigPath(projectName string, parentGroupFullPath string) string { + projectPathString, _ := GenerateGitlabPath(projectName) + ciConfigPath := fmt.Sprintf("%s/%s-ci.yml@platform-one/devops/pipeline-products", parentGroupFullPath, projectPathString) + return ciConfigPath +} diff --git a/custom/p1/types.go b/custom/p1/types.go new file mode 100644 index 0000000000000000000000000000000000000000..c73ab787522e6ba64a80f414743ef6da85e4f030 --- /dev/null +++ b/custom/p1/types.go @@ -0,0 +1,76 @@ +package p1 + +import ( + "fmt" +) + +// ProjectCredentials - +type ProjectCredentials struct { + ServerURL string + ServerToken string +} + +// enumeration for pipeline yaml template paths +const ( + templatePathNpm = "templates/npm.yml" + templatePathYarn = "templates/yarn.yml" + templatePathGradle = "templates/gradle.yml" + templatePathMaven = "templates/maven.yml" + templatePathPip = "templates/pip.yml" + templatePathGoLang = "templates/go.yml" + templatePathCpp = "templates/cpp.yml" + templatePathUnknown = "" +) + +// enumeration for tech stack types +const ( + LangTypeNodejsNpm = "LangTypeNodejsNpm" + LangTypeNodejsYarn = "LangTypeNodejsYarn" + LangTypeJavaGradle = "LangTypeJavaGradle" + LangTypeJavaMaven = "LangTypeJavaMaven" + LangTypePythonPip = "LangTypePythonPip" + LangTypePythonPipEnv = "LangTypePythonPipEnv" + LangTypeReact = "LangTypeReact" + LangTypeAngular = "LangTypeAngular" + LangTypeGoLang = "LangTypeGoLang" + LangTypeCpp = "LangTypeCpp" + LangTypeUnknown = "LangTypeUnknown" +) + +// LangType - +type LangType struct { + langType string + pipelineTemplatePath string +} + +// LangTypes - map of available stack types in P1 and corresponding pipeline template path +var LangTypes map[string]LangType = map[string]LangType{} + +// initialize langTypes +func init() { + LangTypes[LangTypeNodejsNpm] = LangType{langType: LangTypeNodejsNpm, pipelineTemplatePath: templatePathNpm} + LangTypes[LangTypeNodejsYarn] = LangType{langType: LangTypeNodejsYarn, pipelineTemplatePath: templatePathYarn} + LangTypes[LangTypeJavaGradle] = LangType{langType: LangTypeJavaGradle, pipelineTemplatePath: templatePathGradle} + LangTypes[LangTypeJavaMaven] = LangType{langType: LangTypeJavaMaven, pipelineTemplatePath: templatePathMaven} + LangTypes[LangTypePythonPip] = LangType{langType: LangTypePythonPip, pipelineTemplatePath: templatePathPip} + LangTypes[LangTypePythonPipEnv] = LangType{langType: LangTypePythonPipEnv, pipelineTemplatePath: templatePathPip} + LangTypes[LangTypeReact] = LangType{langType: LangTypeReact, pipelineTemplatePath: templatePathNpm} + LangTypes[LangTypeAngular] = LangType{langType: LangTypeAngular, pipelineTemplatePath: templatePathNpm} + LangTypes[LangTypeGoLang] = LangType{langType: LangTypeGoLang, pipelineTemplatePath: templatePathGoLang} + LangTypes[LangTypeCpp] = LangType{langType: LangTypeCpp, pipelineTemplatePath: templatePathCpp} + LangTypes[LangTypeUnknown] = LangType{langType: LangTypeUnknown, pipelineTemplatePath: templatePathUnknown} +} + +// GetProjectLanguage - +func GetProjectLanguage(langTypeName string) (LangType, error) { + langType, ok := LangTypes[langTypeName] + if !ok { + keys := make([]string, 0, len(LangTypes)) + for k := range LangTypes { + keys = append(keys, k) + } + keysStr := fmt.Sprintf("valid keys %v", keys) + return LangType{}, error(fmt.Errorf(keysStr)) + } + return langType, nil +} diff --git a/custom/p1/types_test.go b/custom/p1/types_test.go new file mode 100644 index 0000000000000000000000000000000000000000..677c001c0cc29fbeaeb03f0198ce09e86a337ec0 --- /dev/null +++ b/custom/p1/types_test.go @@ -0,0 +1,37 @@ +package p1 + +import ( + "reflect" + "testing" +) + +func TestGetProjectLanguage(t *testing.T) { + type args struct { + langTypeName string + } + tests := []struct { + name string + args args + want LangType + wantErr bool + }{ + { + name: "project language test", + args: args{langTypeName: LangTypeCpp}, + want: LangTypes[LangTypeCpp], + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetProjectLanguage(tt.args.langTypeName) + if (err != nil) != tt.wantErr { + t.Errorf("GetProjectLanguage() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetProjectLanguage() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/custom/p1/utils.go b/custom/p1/utils.go new file mode 100644 index 0000000000000000000000000000000000000000..4cc2117620096dcae82590c8b3771497fadcd49f --- /dev/null +++ b/custom/p1/utils.go @@ -0,0 +1,28 @@ +package p1 + +import ( + "regexp" + "strings" +) + +// GenerateGitlabPath - use gitlab name to generate a path +func GenerateGitlabPath(name string) (string, error) { + var re *regexp.Regexp + var path = name + + // remove any leading dash or whitespace + re, _ = regexp.Compile(`^[\s-]+`) + path = re.ReplaceAllString(path, "") + + // remove any trailing dash or whitespace + re, _ = regexp.Compile(`[\s-]+$`) + path = re.ReplaceAllString(path, "") + + // replace any whitespace with dash + re, _ = regexp.Compile(`\s+`) + path = re.ReplaceAllString(path, "-") + + pathLower := strings.ToLower(path) + + return pathLower, nil +} diff --git a/custom/p1/utils_test.go b/custom/p1/utils_test.go new file mode 100644 index 0000000000000000000000000000000000000000..88722d60c24a020c2f2c6d335aceae66cb5c8ae2 --- /dev/null +++ b/custom/p1/utils_test.go @@ -0,0 +1,36 @@ +package p1 + +import "testing" + +func TestGenerateGitlabPath(t *testing.T) { + type args struct { + name string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + {name: "re test 1", args: args{name: "a b c"}, want: "a-b-c", wantErr: false}, + {name: "re test 2", args: args{name: "- a b c - "}, want: "a-b-c", wantErr: false}, + {name: "re test 2", args: args{name: "-a b c- "}, want: "a-b-c", wantErr: false}, + {name: "re test 2", args: args{name: " - - a b c- - "}, want: "a-b-c", wantErr: false}, + {name: "re test 3", args: args{name: "a b c "}, want: "a-b-c", wantErr: false}, + {name: "re test 4", args: args{name: " a b c"}, want: "a-b-c", wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GenerateGitlabPath(tt.args.name) + if (err != nil) != tt.wantErr { + t.Errorf("GenerateGitlabPath() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("GenerateGitlabPath() FAIL '%v', want '%v'", got, tt.want) + } else { + t.Logf("GenerateGitlabPath() PASS input '%s' '%s', want '%s'", tt.args.name, got, tt.want) + } + }) + } +} diff --git a/integration-tests/custom/p1/p1_main_test.go b/integration-tests/custom/p1/p1_main_test.go new file mode 100644 index 0000000000000000000000000000000000000000..de480b8cdc2b1350a8b415681cd8028b525e0c4b --- /dev/null +++ b/integration-tests/custom/p1/p1_main_test.go @@ -0,0 +1,122 @@ + +// +build integration + +package integration + +import ( + "errors" + "fmt" + "net/http" + "os" + "testing" + + "github.com/romana/rlog" + gogitlab "github.com/xanzy/go-gitlab" + gitlab "valkyrie.dso.mil/valkyrie-api/clients/gitlab" +) + +const p1IntegrationRootGroupPath = "integration-root-group" + +var P1Config struct { + gitlabAPIURL string + gitlabAPIToken string + testProjectGroupPath string + integrationRootGroupID int +} + +// TestMain - setup and teardown reusable code. wraps around other tests +func TestMain(m *testing.M) { + err := packageSetup() + if err != nil { + fmt.Printf("packageSetup error: %v", err) + os.Exit(1) + } + resultCode := m.Run() + packageTeardown() + os.Exit(resultCode) +} + +func packageSetup() error { + var err error + // initialize + P1Config.testProjectGroupPath = p1IntegrationRootGroupPath + err = getEnvVariables() + if err != nil { + return err + } + err = addRootGroup() + if err != nil { + return err + } + return nil +} + +func packageTeardown() { + +} + +// getEnvVariables - read environment variables +func getEnvVariables() error { + gitlabAPIURL, ok := os.LookupEnv("GITLAB_API_URL") + if !ok { + return errors.New("env variable GITLAB_API_URL undefinded") + } + + gitlabAPIToken, ok := os.LookupEnv("GITLAB_API_TOKEN") + if !ok { + return errors.New("env variable GITLAB_API_TOKEN undefinded") + } + P1Config.gitlabAPIToken = gitlabAPIToken + P1Config.gitlabAPIURL = gitlabAPIURL + return nil +} + +// addRootGroup - add a integration test root group if it does not exist +func addRootGroup() error { + + c, err := getClient() + if err != nil { + return err + } + + // create root integration test group if does not yet exists + path := P1Config.testProjectGroupPath + name := "integration root group" + description := "Integration Test Root Level Group" + + group, statusCode, err := c.GetGroupByFullPath(&path) + if err != nil { + return err + } + + if statusCode == http.StatusFound { + P1Config.integrationRootGroupID = group.ID + rlog.Debugf("AddGroup %s %s %s %s %d", group.Name, group.Path, group.FullPath, group.WebURL, statusCode) + } else { + groupOpts := gogitlab.CreateGroupOptions{Name: &name, Path: &path, Description: &description} + got, statusCode, err := c.AddGroup(groupOpts) + + if err != nil { + // dont fail if group already exists + rlog.Warnf("AddGroup() error = %v %d", err, statusCode) + } else { + P1Config.integrationRootGroupID = got.ID + rlog.Debugf("AddGroup %s %s %s %s %d", got.Name, got.Path, got.FullPath, got.WebURL, statusCode) + } + } + return nil +} + +// getClientGroups - +func getClient() (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 +} diff --git a/integration-tests/custom/p1/p1_projects_test.go b/integration-tests/custom/p1/p1_projects_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ded9acd3cbcf7d5524872f88e45a0545c0fcad08 --- /dev/null +++ b/integration-tests/custom/p1/p1_projects_test.go @@ -0,0 +1,85 @@ +// +build integration + +package integration + +import ( + "testing" + + "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" + custom_p1 "valkyrie.dso.mil/valkyrie-api/custom/p1" +) + +/* + +P1Config is initialized in the TestMain wrapper method + +*/ + +func Test_P1_AddProject(t *testing.T) { + logPrefix := "Test_P1_AddProject" + t.Run("test", func(t *testing.T) { + projectSpec := v1alpha1.ProjectSpec{ + Name: "My Test Project", + GroupID: int64(P1Config.integrationRootGroupID), + Language: custom_p1.LangTypeAngular, + } + + creds := custom_p1.ProjectCredentials{ + ServerURL: P1Config.gitlabAPIURL, + ServerToken: P1Config.gitlabAPIToken, + } + + err := custom_p1.CreateProject(creds, projectSpec, nil) + if err != nil { + t.Errorf("CreateProject() error = %v", err) + return + } + + t.Logf("%s created project %v", logPrefix, projectSpec) + }) +} + +func Test_P1_UpdateProject(t *testing.T) { + logPrefix := "Test_P1_UpdateProject" + t.Run("test", func(t *testing.T) { + projectSpec := v1alpha1.ProjectSpec{ + Name: "My Test Project", + GroupID: int64(P1Config.integrationRootGroupID), + Language: custom_p1.LangTypeJavaMaven, + } + + creds := custom_p1.ProjectCredentials{ + ServerURL: P1Config.gitlabAPIURL, + ServerToken: P1Config.gitlabAPIToken, + } + + err := custom_p1.UpdateProject(creds, projectSpec, nil) + if err != nil { + t.Errorf("UpdateProject() error = %v", err) + return + } + t.Logf("%s update project %v", logPrefix, projectSpec) + }) +} + +func Test_P1_DeleteProject(t *testing.T) { + logPrefix := "Test_P1_DeleteProject" + t.Run("test", func(t *testing.T) { + projectSpec := v1alpha1.ProjectSpec{ + Name: "My Test Project", + GroupID: int64(P1Config.integrationRootGroupID), + } + + creds := custom_p1.ProjectCredentials{ + ServerURL: P1Config.gitlabAPIURL, + ServerToken: P1Config.gitlabAPIToken, + } + + err := custom_p1.DeleteProject(creds, projectSpec, nil) + if err != nil { + t.Errorf("DeleteProject() error = %v", err) + return + } + t.Logf("%s deleted project %v", logPrefix, projectSpec) + }) +} diff --git a/integration-tests/gitlab/api/gitlab_api_projects_test.go b/integration-tests/gitlab/api/gitlab_api_projects_test.go index b4c4d3aeabbeff251b420256cc0cccf2f989a10f..a14fd8557a85b21579db21dadc3f0feed4b10840 100644 --- a/integration-tests/gitlab/api/gitlab_api_projects_test.go +++ b/integration-tests/gitlab/api/gitlab_api_projects_test.go @@ -16,7 +16,7 @@ import ( const projectsTestItemCount = 65 const testProjectsPrefix = "IntTestProject" -var integrationRootGroupID = 0 +var integrationRootGroupIDProjects = 0 // getClientProjects - func getClientProjects() (gitlab.Client, error) { @@ -68,7 +68,7 @@ func TestClient_GetProjects_Search(t *testing.T) { } // Insure the integration test root level group exists -func TestClient_AddRootGroup(t *testing.T) { +func TestClient_AddRootGroup_Projects(t *testing.T) { t.Run("test", func(t *testing.T) { c, err := getClientProjects() if err != nil { @@ -88,7 +88,7 @@ func TestClient_AddRootGroup(t *testing.T) { } if statusCode == http.StatusFound { - integrationRootGroupID = group.ID + integrationRootGroupIDProjects = group.ID t.Logf("AddGroup %s %s %s %s %d", group.Name, group.Path, group.FullPath, group.WebURL, statusCode) } else { groupOpts := gogitlab.CreateGroupOptions{Name: &name, Path: &path, Description: &description} @@ -98,7 +98,7 @@ func TestClient_AddRootGroup(t *testing.T) { // dont fail if group already exists t.Logf("Client.AddGroup() error = %v %d", err, statusCode) } else { - integrationRootGroupID = got.ID + integrationRootGroupIDProjects = got.ID t.Logf("AddGroup %s %s %s %s %d", got.Name, got.Path, got.FullPath, got.WebURL, statusCode) } } @@ -118,12 +118,14 @@ func TestClient_AddProjects(t *testing.T) { path := testProjectsPrefix + "-project-" + fmt.Sprintf("%d", i) name := testProjectsPrefix + " project " + fmt.Sprintf("%d", i) description := "My fancy test project " + fmt.Sprintf("%d", i) + configPath := "a/b/config.yml@group1/group2/project" projectOpts := gogitlab.CreateProjectOptions{ Name: &name, Path: &path, Description: &description, - NamespaceID: &integrationRootGroupID, + NamespaceID: &integrationRootGroupIDProjects, + CIConfigPath: &configPath, } got, statusCode, err := c.AddProject(projectOpts) @@ -154,7 +156,7 @@ func TestClient_UpdateProject(t *testing.T) { Name: &name, Path: &path, Description: &description, - NamespaceID: &integrationRootGroupID, + NamespaceID: &integrationRootGroupIDProjects, } newProject, statusCode, err := c.AddProject(projectOpts) if err != nil { diff --git a/scripts/template.sh b/scripts/template.sh deleted file mode 100755 index f36ceeaf9fa3e3da5f363b7c97669e6e5e8feea2..0000000000000000000000000000000000000000 --- a/scripts/template.sh +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env bash -########################################## -# Boilerplate -# Author: Adam Richards -########################################## -SCRIPT=$(basename "${0}") -SCRIPTPATH=$(cd "$(dirname "${0}")" && pwd -P) -FULLSCRIPT="${SCRIPTPATH}/${SCRIPT}" -NO_ROOT=1 -########################################## -echo_stderr() { - # echo to stderr - echo "$@" 1>&2 -} -########################################## -if ((NO_ROOT == 1)); then - id=$(id -u) - if [[ "$id" -eq "0" ]]; then - echo_stderr "Running as root not allowed" - exit 2 - fi -fi -########################################## -function interrupt_handler() { - echo_stderr "INTERRUPT Received!" - # cleanup - exit 3 -} -# bind function to interrupts SIGINT and SIGTERM: -trap interrupt_handler SIGINT -trap interrupt_handler SIGTERM -########################################## -function syntax() { - echo_stderr "Syntax $SCRIPT args args args" - echo_stderr "ex: $SCRIPT args args args" - echo_stderr "SCRIPT: $SCRIPT" - echo_stderr "SCRIPTPATH: $SCRIPTPATH" - echo_stderr "FULLSCRIPT: $FULLSCRIPT" - exit 1 -} -########################################## -# using getopts rather than getopt as getopt may not be available on system -while getopts ":hn:t:" options; do # Loop: Get the next option; - # use silent error checking- leading ':'; - # options that take args are followed by ':' - case "${options}" in - h) # If the option is n, - syntax - ;; - n) - NAME=${OPTARG} - ;; - t) - TIMES=${OPTARG} # Set $TIMES to specified value. - re_isanum='^[0-9]+$' # Regex: match whole numbers only - if ! [[ $TIMES =~ $re_isanum ]]; then # if $TIMES not a whole number: - echo "Error: TIMES must be a positive, whole number." - syntax - exit 1 - elif [ "$TIMES" -eq "0" ]; then # If it's zero: - echo "Error: TIMES must be greater than zero." - syntax # Exit abnormally. - fi - ;; - :) # If expected argument omitted: - echo_stderr "Error: option -${OPTARG} requires an argument." - syntax - ;; - *) # If unknown (any other) option: - echo_stderr "Error: unknown option -${OPTARG}." - syntax - ;; - esac -done -########################################## -echo "$NAME" -echo "$TIMES" -# Do The Work -M=3 -while ( $TRUE ) && (($M > 0)); do - echo "loop $M" - M=$((M - 1)) - sleep 2 -done -########################################## -## loops that will properly handle files with spaces and backslash -find . -name "*.*" -print0 | while read -rd $'\0' file; do - echo "FILE: $file" -done -while read -rd $'\0' file; do - echo "FILE: $file" -done < <(find . -type f -name '*.*' -print0)