internal/provider/testutil/helpers.go (1,014 lines of code) (raw):

//go:build acceptance || flakey || settings // +build acceptance flakey settings package testutil import ( "context" "encoding/base64" "fmt" "io" "os" "strings" "testing" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/onsi/gomega" gitlab "gitlab.com/gitlab-org/api/client-go" "gitlab.com/gitlab-org/terraform-provider-gitlab/internal/provider/api" "gitlab.com/gitlab-org/terraform-provider-gitlab/internal/provider/utils" ) type SkipFunc = func() (bool, error) var testGitlabConfig = api.Config{ Token: os.Getenv("GITLAB_TOKEN"), BaseURL: os.Getenv("GITLAB_BASE_URL"), CACertFile: "", Insecure: false, ClientCert: "", ClientKey: "", EarlyAuthFail: false, Headers: nil, } var TestGitlabClient *gitlab.Client func init() { client, err := testGitlabConfig.NewGitLabClient(context.Background()) if err != nil { panic("failed to create test client: " + err.Error()) // lintignore: R009 // TODO: Resolve this tfproviderlint issue } TestGitlabClient = client // We are using the gomega package for its matchers only, but it requires us to register a handler anyway. gomega.RegisterFailHandler(func(_ string, _ ...int) { panic("gomega fail handler should not be used") // lintignore: R009 }) } // Global variable to cache the result of EE evaluation for all the tests var isEE *bool // Returns true if the acceptance test is running Gitlab EE. // Meant to be used as SkipFunc to skip tests that work only on Gitlab CE. func IsRunningInEE() (bool, error) { if isEE != nil { return *isEE, nil } eeContext, err := utils.IsRunningInEEContext(TestGitlabClient) if err != nil { return false, err } isEE := gitlab.Ptr(eeContext) return *isEE, err } // IsRunningInCE returns true if the acceptance test is running Gitlab CE. // Meant to be used as SkipFunc to skip tests that work only on Gitlab EE. func IsRunningInCE() (bool, error) { isEE, err := IsRunningInEE() return !isEE, err } // SkipIfCE is a test helper that skips the current test if the GitLab version is not GitLab Enterprise. // This is useful when the version needs to be checked during setup, before the Terraform acceptance test starts. func SkipIfCE(t *testing.T) { t.Helper() isCE, err := IsRunningInCE() if err != nil { t.Fatalf("could not check GitLab version is CE: %v", err) } if isCE { t.Skipf("Test is skipped for CE (non-Enterprise) version of GitLab") } } func SkipIfEE(t *testing.T) { t.Helper() isEE, err := IsRunningInEE() if err != nil { t.Fatalf("could not check GitLab version is EE: %v", err) } if isEE { t.Skipf("Test is skipped for EE (Enterprise) version of GitLab") } } func RunIfLessThan(t *testing.T, requiredMaxVersion string) { isLessThan, err := api.IsGitLabVersionLessThan(context.Background(), TestGitlabClient, requiredMaxVersion)() if err != nil { t.Fatalf("Failed to fetch GitLab version: %+v", err) } if !isLessThan { t.Skipf("This test is only valid for GitLab versions less than %s", requiredMaxVersion) } } func RunIfAtLeast(t *testing.T, requiredMinVersion string) { isAtLeast, err := api.IsGitLabVersionAtLeast(context.Background(), TestGitlabClient, requiredMinVersion)() if err != nil { t.Fatalf("Failed to fetch GitLab version: %+v", err) } if !isAtLeast { t.Skipf("This test is only valid for GitLab versions newer than %s", requiredMinVersion) } } func IsRunningAtLeast(t *testing.T, requiredMinVersion string) bool { isAtLeast, err := api.IsGitLabVersionAtLeast(context.Background(), TestGitlabClient, requiredMinVersion)() if err != nil { t.Fatalf("Failed to fetch GitLab version: %+v", err) } return isAtLeast } // GetCurrentUser is a test helper for getting the current user of the provided client. func GetCurrentUser(t *testing.T) *gitlab.User { t.Helper() user, _, err := TestGitlabClient.Users.CurrentUser() if err != nil { t.Fatalf("could not get current user: %v", err) } return user } // CreateProject is a test helper for creating a project. func CreateProject(t *testing.T) *gitlab.Project { return CreateProjectWithNamespace(t, 0) } // CreateProjectWithNamespace is a test helper for creating a project. This method accepts a namespace to create a project // within a group func CreateProjectWithNamespace(t *testing.T, namespaceID int) *gitlab.Project { t.Helper() options := &gitlab.CreateProjectOptions{ Name: gitlab.Ptr(acctest.RandomWithPrefix("acctest")), Description: gitlab.Ptr("Terraform acceptance tests"), // So that acceptance tests can be run in a gitlab organization with no billing. Visibility: gitlab.Ptr(gitlab.PublicVisibility), // So that a branch is created. InitializeWithReadme: gitlab.Ptr(true), } // Apply a namespace if one is passed in. if namespaceID != 0 { options.NamespaceID = gitlab.Ptr(namespaceID) } return CreateProjectWithOptions(t, options) } // CreateProjectWithOptions is a test helper for creating a project given some options func CreateProjectWithOptions(t *testing.T, opts *gitlab.CreateProjectOptions) *gitlab.Project { t.Helper() project, _, err := TestGitlabClient.Projects.CreateProject(opts) if err != nil { t.Fatalf("could not create test project: %v", err) } t.Cleanup(func() { if _, err := TestGitlabClient.Projects.DeleteProject(project.ID, nil); err != nil { t.Fatalf("could not cleanup test project: %v", err) } }) return project } func CreateProjectWithDefaultPushRules(t *testing.T, namespaceID int) *gitlab.Project { t.Helper() project := CreateProjectWithNamespace(t, namespaceID) /* Setting the default options based on what is set on gitlab.com when a new project is created. The following are the push rules returned via the push_rule API after a project is created: { "id": 123, "project_id": 42, "created_at": "2024-07-25T15:49:50.119Z", "commit_message_regex": "", "commit_message_negative_regex": null, "branch_name_regex": null, "deny_delete_tag": false, "member_check": false, "prevent_secrets": false, "author_email_regex": "", "file_name_regex": "", "max_file_size": 0, "commit_committer_check": null, "commit_committer_name_check": false, "reject_unsigned_commits": null, "reject_non_dco_commits": null } */ options := &gitlab.AddProjectPushRuleOptions{ AuthorEmailRegex: nil, BranchNameRegex: nil, CommitCommitterCheck: nil, CommitCommitterNameCheck: gitlab.Ptr(false), CommitMessageNegativeRegex: nil, CommitMessageRegex: nil, DenyDeleteTag: gitlab.Ptr(false), FileNameRegex: nil, MaxFileSize: gitlab.Ptr(0), MemberCheck: gitlab.Ptr(false), PreventSecrets: gitlab.Ptr(false), RejectUnsignedCommits: nil, RejectNonDCOCommits: nil, } _, _, err := TestGitlabClient.Projects.AddProjectPushRule(project.ID, options) if err != nil { t.Fatalf("could not create test project with default push rules") } return project } // CreateProjectMirror is a test helper for creating a project mirror. // It assumes the project will be destroyed at the end of the test and will not cleanup created mirrors. func CreateProjectMirrorWithOptions(t *testing.T, project *gitlab.Project, opts *gitlab.AddProjectMirrorOptions) *gitlab.ProjectMirror { t.Helper() var err error projectMirror, _, err := TestGitlabClient.ProjectMirrors.AddProjectMirror(project.ID, opts) if err != nil { t.Fatalf("could not create project mirror: %v", err) } return projectMirror } // CreateTopic is a test helper for creating a topic. func CreateTopic(t *testing.T) *gitlab.Topic { t.Helper() name := gitlab.Ptr(acctest.RandomWithPrefix("acctest")) options := &gitlab.CreateTopicOptions{ Name: name, Title: name, } topic, _, err := TestGitlabClient.Topics.CreateTopic(options) if err != nil { t.Fatalf("could not create test topic: %v", err) } t.Cleanup(func() { if _, err := TestGitlabClient.Topics.DeleteTopic(topic.ID); err != nil { t.Fatalf("could not cleanup test topic: %v", err) } }) return topic } // CreateUsers is a test helper for creating a specified number of users. func CreateUsers(t *testing.T, n int) []*gitlab.User { return CreateUsersWithPrefix(t, n, "acctest-user") } func CreateUsersWithPrefix(t *testing.T, n int, prefix string) []*gitlab.User { t.Helper() users := make([]*gitlab.User, n) for i := range users { var err error username := acctest.RandomWithPrefix(prefix) users[i], _, err = TestGitlabClient.Users.CreateUser(&gitlab.CreateUserOptions{ Name: gitlab.Ptr(username), Username: gitlab.Ptr(username), Email: gitlab.Ptr(username + "@example.com"), Password: gitlab.Ptr(acctest.RandString(16)), SkipConfirmation: gitlab.Ptr(true), }) if err != nil { t.Fatalf("could not create test user (username=%q): %v", username, err) } userID := users[i].ID // Needed for closure. t.Cleanup(func() { if _, err := TestGitlabClient.Users.DeleteUser(userID); err != nil { t.Fatalf("could not cleanup test user: %v", err) } }) } return users } // Create Personal Access Token for a given user with `api` scope func CreatePersonalAccessToken(t *testing.T, user *gitlab.User) *gitlab.PersonalAccessToken { t.Helper() return CreatePersonalAccessTokenWithScopes(t, user, []string{"api"}) } // Create Personal Access Token for a given user with specified scopes func CreatePersonalAccessTokenWithScopes(t *testing.T, user *gitlab.User, scopes []string) *gitlab.PersonalAccessToken { t.Helper() token, _, err := TestGitlabClient.Users.CreatePersonalAccessToken(user.ID, &gitlab.CreatePersonalAccessTokenOptions{Name: gitlab.Ptr(acctest.RandomWithPrefix("acctest")), Scopes: &scopes}) if err != nil { t.Fatalf("could not create Personal Access Token for user %d", user.ID) } return token } // CreateGroups is a test helper for creating a specified number of groups. func CreateGroups(t *testing.T, n int) []*gitlab.Group { t.Helper() return CreateGroupsWithPrefix(t, n, "acctest-group") } // CreateGroupsWithPrefix is a test helper for creating a specified number of groups with specific prefix. func CreateGroupsWithPrefix(t *testing.T, n int, prefix string) []*gitlab.Group { t.Helper() groups := make([]*gitlab.Group, n) for i := range groups { var err error name := acctest.RandomWithPrefix(prefix) groups[i], _, err = TestGitlabClient.Groups.CreateGroup(&gitlab.CreateGroupOptions{ Name: gitlab.Ptr(name), Path: gitlab.Ptr(name), // So that acceptance tests can be run in a gitlab organization with no billing. Visibility: gitlab.Ptr(gitlab.PublicVisibility), }) if err != nil { t.Fatalf("could not create test group: %v", err) } groupID := groups[i].ID // Needed for closure. t.Cleanup(func() { if _, err := TestGitlabClient.Groups.DeleteGroup(groupID, nil); err != nil { t.Fatalf("could not cleanup test group: %v", err) } }) } return groups } // CreateSubGroupsWithPrefix is a test helper for creating a specified number of subgroups with specific prefix. func CreateSubGroupsWithPrefix(t *testing.T, parentGroup *gitlab.Group, n int, prefix string) []*gitlab.Group { t.Helper() groups := make([]*gitlab.Group, n) for i := range groups { var err error name := acctest.RandomWithPrefix(prefix) groups[i], _, err = TestGitlabClient.Groups.CreateGroup(&gitlab.CreateGroupOptions{ Name: gitlab.Ptr(name), Path: gitlab.Ptr(name), // So that acceptance tests can be run in a gitlab organization with no billing. Visibility: gitlab.Ptr(gitlab.PublicVisibility), ParentID: gitlab.Ptr(parentGroup.ID), }) if err != nil { t.Fatalf("could not create test subgroup: %v", err) } } return groups } // CreateSubGroups is a test helper for creating a specified number of subgroups. func CreateSubGroups(t *testing.T, parentGroup *gitlab.Group, n int) []*gitlab.Group { t.Helper() return CreateSubGroupsWithPrefix(t, parentGroup, n, "acctest-group") } func CreateGroupHooks(t *testing.T, gid interface{}, n int) []*gitlab.GroupHook { t.Helper() var hooks []*gitlab.GroupHook for i := 0; i < n; i++ { hook, _, err := TestGitlabClient.Groups.AddGroupHook(gid, &gitlab.AddGroupHookOptions{ URL: gitlab.Ptr(fmt.Sprintf("https://%s.com", acctest.RandomWithPrefix("acctest"))), }) if err != nil { t.Fatalf("could not create group hook: %v", err) } hooks = append(hooks, hook) } return hooks } // CreateBranches is a test helper for creating a specified number of branches. // It assumes the project will be destroyed at the end of the test and will not cleanup created branches. func CreateBranches(t *testing.T, project *gitlab.Project, n int) []*gitlab.Branch { t.Helper() branches := make([]*gitlab.Branch, n) for i := range branches { var err error branches[i], _, err = TestGitlabClient.Branches.CreateBranch(project.ID, &gitlab.CreateBranchOptions{ Branch: gitlab.Ptr(acctest.RandomWithPrefix("acctest")), Ref: gitlab.Ptr(project.DefaultBranch), }) if err != nil { t.Fatalf("could not create test branches: %v", err) } } return branches } // CreateProtectedBranches is a test helper for creating a specified number of protected branches. // It assumes the project will be destroyed at the end of the test and will not cleanup created branches. func CreateProtectedBranches(t *testing.T, project *gitlab.Project, n int) []*gitlab.ProtectedBranch { t.Helper() branches := CreateBranches(t, project, n) protectedBranches := make([]*gitlab.ProtectedBranch, n) for i := range make([]int, n) { var err error protectedBranches[i], _, err = TestGitlabClient.ProtectedBranches.ProtectRepositoryBranches(project.ID, &gitlab.ProtectRepositoryBranchesOptions{ Name: gitlab.Ptr(branches[i].Name), }) if err != nil { t.Fatalf("could not protect test branches: %v", err) } } return protectedBranches } // CreateTags is a test helper for creating a specified number of tags. // It assumes the project will be destroyed at the end of the test and will not cleanup created tags. func CreateTags(t *testing.T, project *gitlab.Project, n int) []*gitlab.Tag { t.Helper() tags := make([]*gitlab.Tag, n) for i := range tags { var err error tags[i], _, err = TestGitlabClient.Tags.CreateTag(project.ID, &gitlab.CreateTagOptions{ TagName: gitlab.Ptr(acctest.RandomWithPrefix("acctest")), Ref: gitlab.Ptr(project.DefaultBranch), }) if err != nil { t.Fatalf("could not create test tags: %v", err) } } return tags } // CreateProtectedTags is a test helper for creating a specified number of protected tags. // It assumes the project will be destroyed at the end of the test and will not cleanup created tags. func CreateProtectedTags(t *testing.T, project *gitlab.Project, n int) []*gitlab.ProtectedTag { t.Helper() tags := CreateTags(t, project, n) protectedTags := make([]*gitlab.ProtectedTag, n) for i := range make([]int, n) { var err error protectedTags[i], _, err = TestGitlabClient.ProtectedTags.ProtectRepositoryTags(project.ID, &gitlab.ProtectRepositoryTagsOptions{ Name: gitlab.Ptr(tags[i].Name), }) if err != nil { t.Fatalf("could not protect test tags: %v", err) } } return protectedTags } // CreateProtectedTagWithOptions is a test helper for creating a protected tag with specified options. // It assumes the project will be destroyed at the end of the test and will not cleanup created tags. func CreateProtectedTagWithOptions(t *testing.T, project *gitlab.Project, opts *gitlab.ProtectRepositoryTagsOptions) *gitlab.ProtectedTag { t.Helper() tagName := acctest.RandomWithPrefix("acctest") // use the name provided on the options, otherwise if not provided use a random one if opts.Name != nil && *opts.Name != "" { tagName = *opts.Name } else { opts.Name = gitlab.Ptr(tagName) } _, _, err := TestGitlabClient.Tags.CreateTag(project.ID, &gitlab.CreateTagOptions{ TagName: gitlab.Ptr(tagName), Ref: gitlab.Ptr(project.DefaultBranch), }) if err != nil { t.Fatalf("could not create test tag: %v", err) } protectedTag, _, err := TestGitlabClient.ProtectedTags.ProtectRepositoryTags(project.ID, opts) if err != nil { t.Fatalf("could not protect test tag: %v", err) } return protectedTag } // CreateReleases is a test helper for creating a specified number of releases. // It assumes the project will be destroyed at the end of the test and will not cleanup created releases. func CreateReleases(t *testing.T, project *gitlab.Project, n int) []*gitlab.Release { t.Helper() releases := make([]*gitlab.Release, n) linkType := gitlab.LinkTypeValue("other") linkURL1 := fmt.Sprintf("https://test/%v", *gitlab.Ptr(acctest.RandomWithPrefix("acctest"))) linkURL2 := fmt.Sprintf("https://test/%v", *gitlab.Ptr(acctest.RandomWithPrefix("acctest"))) for i := range releases { var err error releases[i], _, err = TestGitlabClient.Releases.CreateRelease(project.ID, &gitlab.CreateReleaseOptions{ Name: gitlab.Ptr(acctest.RandomWithPrefix("acctest")), TagName: gitlab.Ptr(acctest.RandomWithPrefix("acctest")), Ref: &project.DefaultBranch, Assets: &gitlab.ReleaseAssetsOptions{ Links: []*gitlab.ReleaseAssetLinkOptions{ { Name: gitlab.Ptr(acctest.RandomWithPrefix("acctest")), URL: &linkURL1, LinkType: &linkType, }, { Name: gitlab.Ptr(acctest.RandomWithPrefix("acctest")), URL: &linkURL2, LinkType: &linkType, }, }, }, }) if err != nil { t.Fatalf("could not create test releases: %v", err) } } return releases } // CreateMergeRequest is a test helper for creating a merge request. // It assumes that the project will be destroyed at the end of the test and will not cleanup created merge requests. func CreateMergeRequest(t *testing.T, assignee *gitlab.User, project *gitlab.Project, source string, target string) *gitlab.MergeRequest { opts := gitlab.CreateMergeRequestOptions{ Title: gitlab.Ptr(acctest.RandomWithPrefix(source)), Description: gitlab.Ptr(acctest.RandomWithPrefix(source)), SourceBranch: &source, TargetBranch: &target, } if assignee != nil { opts.AssigneeID = &assignee.ID opts.AssigneeIDs = &[]int{assignee.ID} } mergeRequest, _, err := TestGitlabClient.MergeRequests.CreateMergeRequest( project.ID, &opts, ) if err != nil { t.Fatalf("could not create merge request: %v", err) } return mergeRequest } // CloseMergeRequest is a test helper for closing a merge request. // It assumes that the project will be destroyed at the end of the test and will not cleanup closed merge requests. func CloseMergeRequest(t *testing.T, project *gitlab.Project, mr *gitlab.MergeRequest) *gitlab.MergeRequest { opts := gitlab.UpdateMergeRequestOptions{StateEvent: gitlab.Ptr("close")} mergeRequest, _, err := TestGitlabClient.MergeRequests.UpdateMergeRequest( project.ID, mr.IID, &opts, ) if err != nil { t.Fatalf("could not close merge request: %v", err) } return mergeRequest } // AddProjectMembers is a test helper for adding users as members of a project with Developer access level. // It assumes the project will be destroyed at the end of the test and will not cleanup members. func AddProjectMembers(t *testing.T, pid interface{}, users []*gitlab.User) { t.Helper() AddProjectMembersWithAccessLevel(t, pid, users, gitlab.DeveloperPermissions) } // AddProjectMembersWithAccessLevel is a test helper for adding users as members of a project with a given access level. // It assumes the project will be destroyed at the end of the test and will not cleanup members. func AddProjectMembersWithAccessLevel(t *testing.T, pid interface{}, users []*gitlab.User, accessLevel gitlab.AccessLevelValue) { t.Helper() for _, user := range users { _, _, err := TestGitlabClient.ProjectMembers.AddProjectMember(pid, &gitlab.AddProjectMemberOptions{ UserID: user.ID, AccessLevel: gitlab.Ptr(accessLevel), }) if err != nil { t.Fatalf("could not add test project member: %v", err) } } } func CreateProjectHooks(t *testing.T, pid interface{}, n int) []*gitlab.ProjectHook { t.Helper() var hooks []*gitlab.ProjectHook for i := 0; i < n; i++ { hook, _, err := TestGitlabClient.Projects.AddProjectHook(pid, &gitlab.AddProjectHookOptions{ URL: gitlab.Ptr(fmt.Sprintf("https://%s.com", acctest.RandomWithPrefix("acctest"))), }) if err != nil { t.Fatalf("could not create project hook: %v", err) } hooks = append(hooks, hook) } return hooks } func CreateClusterAgents(t *testing.T, pid interface{}, n int) []*gitlab.Agent { t.Helper() var clusterAgents []*gitlab.Agent for i := 0; i < n; i++ { clusterAgent, _, err := TestGitlabClient.ClusterAgents.RegisterAgent(pid, &gitlab.RegisterAgentOptions{ Name: gitlab.Ptr(fmt.Sprintf("agent-%d", i)), }) if err != nil { t.Fatalf("could not create test cluster agent: %v", err) } t.Cleanup(func() { _, err := TestGitlabClient.ClusterAgents.DeleteAgent(pid, clusterAgent.ID) if err != nil { t.Fatalf("could not cleanup test cluster agent: %v", err) } }) clusterAgents = append(clusterAgents, clusterAgent) } return clusterAgents } func SetupUserAccess(t *testing.T, project *gitlab.Project, agent *gitlab.Agent) { t.Helper() agentCfgPath := fmt.Sprintf(".gitlab/agents/%s/config.yaml", agent.Name) userAccessCfg := []byte(fmt.Sprintf(` user_access: access_as: agent: {} projects: - id: %q `, project.PathWithNamespace)) _, _, err := TestGitlabClient.RepositoryFiles.CreateFile(project.ID, agentCfgPath, &gitlab.CreateFileOptions{ Branch: gitlab.Ptr(project.DefaultBranch), Encoding: gitlab.Ptr("base64"), Content: gitlab.Ptr(base64.StdEncoding.EncodeToString(userAccessCfg)), CommitMessage: gitlab.Ptr(fmt.Sprintf("Setup user access for agent %s in acceptance tests", agent.Name)), }) if err != nil { t.Fatalf("unable to setup user access for agent %s in project %s: %v", agent.Name, project.PathWithNamespace, err) } } func CreateProjectIssues(t *testing.T, pid interface{}, n int) []*gitlab.Issue { t.Helper() dueDate := gitlab.ISOTime(time.Now().Add(time.Hour)) var issues []*gitlab.Issue for i := 0; i < n; i++ { issue, _, err := TestGitlabClient.Issues.CreateIssue(pid, &gitlab.CreateIssueOptions{ Title: gitlab.Ptr(fmt.Sprintf("Issue %d", i)), Description: gitlab.Ptr(fmt.Sprintf("Description %d", i)), DueDate: &dueDate, }) if err != nil { t.Fatalf("could not create test issue: %v", err) } issues = append(issues, issue) } return issues } func CreateGroupEpicBoard(t *testing.T, path string) { t.Helper() query := gitlab.GraphQLQuery{ Query: fmt.Sprintf(` mutation { epicBoardCreate( input: { groupPath: "%s", name: "%s" } ) { epicBoard { id, name } errors } }`, path, acctest.RandomWithPrefix("acctest")), } ctx := context.Background() var pid interface{} if _, err := TestGitlabClient.GraphQL.Do(ctx, query, &pid); err != nil { t.Fatalf("Unable to create epic board: %s", err.Error()) } } func CreateGroupIssueBoard(t *testing.T, pid interface{}) *gitlab.GroupIssueBoard { t.Helper() issueBoard, _, err := TestGitlabClient.GroupIssueBoards.CreateGroupIssueBoard(pid, &gitlab.CreateGroupIssueBoardOptions{Name: gitlab.Ptr(acctest.RandomWithPrefix("acctest"))}) if err != nil { t.Fatalf("could not create test group issue board: %v", err) } return issueBoard } func CreateProjectIssueBoard(t *testing.T, pid interface{}) *gitlab.IssueBoard { t.Helper() issueBoard, _, err := TestGitlabClient.Boards.CreateIssueBoard(pid, &gitlab.CreateIssueBoardOptions{Name: gitlab.Ptr(acctest.RandomWithPrefix("acctest"))}) if err != nil { t.Fatalf("could not create test issue board: %v", err) } return issueBoard } func CreateGroupLabels(t *testing.T, pid interface{}, n int) []*gitlab.GroupLabel { t.Helper() var labels []*gitlab.GroupLabel for i := 0; i < n; i++ { label, _, err := TestGitlabClient.GroupLabels.CreateGroupLabel(pid, &gitlab.CreateGroupLabelOptions{Name: gitlab.Ptr(acctest.RandomWithPrefix("acctest")), Color: gitlab.Ptr("#000000")}) if err != nil { t.Fatalf("could not create test group label: %v", err) } labels = append(labels, label) } return labels } func CreateProjectLabels(t *testing.T, pid interface{}, n int) []*gitlab.Label { t.Helper() var labels []*gitlab.Label for i := 0; i < n; i++ { label, _, err := TestGitlabClient.Labels.CreateLabel(pid, &gitlab.CreateLabelOptions{Name: gitlab.Ptr(acctest.RandomWithPrefix("acctest")), Color: gitlab.Ptr("#000000")}) if err != nil { t.Fatalf("could not create test label: %v", err) } labels = append(labels, label) } return labels } // AddGroupMembers is a test helper for adding users as members of a group with Developer level access. // It assumes the group will be destroyed at the end of the test and will not cleanup members. func AddGroupMembers(t *testing.T, gid interface{}, users []*gitlab.User) { t.Helper() AddGroupMembersWithAccessLevel(t, gid, users, gitlab.DeveloperPermissions) } // GroupShareGroup shares a group with another group with a developer access level and finite date. func GroupShareGroup(t *testing.T, parentGid interface{}, sharedGid *int) *gitlab.Group { t.Helper() endDate := time.Date(2023, 12, 21, 0, 0, 0, 0, time.UTC) exp := gitlab.ISOTime(endDate) group, _, err := TestGitlabClient.Groups.ShareGroupWithGroup(parentGid, &gitlab.ShareGroupWithGroupOptions{ GroupID: sharedGid, GroupAccess: gitlab.Ptr(gitlab.DeveloperPermissions), ExpiresAt: &exp, }) if err != nil { t.Fatalf("could not share group with group: %v", err) } return group } // AddGroupMembersWithAccessLevel is a test helper for adding users as members of a group with a given access level. func AddGroupMembersWithAccessLevel(t *testing.T, gid interface{}, users []*gitlab.User, accessLevel gitlab.AccessLevelValue) { t.Helper() for _, user := range users { _, _, err := TestGitlabClient.GroupMembers.AddGroupMember(gid, &gitlab.AddGroupMemberOptions{ UserID: gitlab.Ptr(user.ID), AccessLevel: gitlab.Ptr(accessLevel), }) if err != nil { t.Fatalf("could not add test group member: %v", err) } } } // ProjectShareGroup is a test helper for sharing a project with a group. func ProjectShareGroup(t *testing.T, pid interface{}, gid int) { t.Helper() _, err := TestGitlabClient.Projects.ShareProjectWithGroup(pid, &gitlab.ShareWithGroupOptions{ GroupID: gitlab.Ptr(gid), GroupAccess: gitlab.Ptr(gitlab.DeveloperPermissions), }) if err != nil { t.Fatalf("could not share project %v with group %d: %v", pid, gid, err) } } // List project members func ListProjectMembers(t *testing.T, pid interface{}) { t.Helper() members, _, err := TestGitlabClient.ProjectMembers.ListAllProjectMembers(pid, &gitlab.ListProjectMembersOptions{}) if err != nil { t.Fatalf("could not get project %d member list: %v", pid, err) } t.Log("--------------------------------------------------------") t.Log("Project member list") for _, member := range members { t.Logf("\nUserId: `%d`, accessLevel: `%d`, state: `%s`", member.ID, member.AccessLevel, member.State) } t.Log("\n--------------------------------------------------------") } // AddProjectMilestones is a test helper for adding milestones to project. // It assumes the group will be destroyed at the end of the test and will not cleanup milestones. func AddProjectMilestones(t *testing.T, project *gitlab.Project, n int) []*gitlab.Milestone { t.Helper() milestones := make([]*gitlab.Milestone, n) for i := range milestones { var err error milestones[i], _, err = TestGitlabClient.Milestones.CreateMilestone(project.ID, &gitlab.CreateMilestoneOptions{ Title: gitlab.Ptr(fmt.Sprintf("Milestone %d", i)), Description: gitlab.Ptr(fmt.Sprintf("Description %d", i)), }) if err != nil { t.Fatalf("Could not create test milestones: %v", err) } } return milestones } func AddGroupMilestones(t *testing.T, group *gitlab.Group, n int) []*gitlab.GroupMilestone { t.Helper() milestones := make([]*gitlab.GroupMilestone, n) for i := range milestones { var err error startDate := time.Date(2023, 8, 1, 0, 0, 0, 0, time.UTC) x := gitlab.ISOTime(startDate) endDate := time.Date(2023, 9, 1, 0, 0, 0, 0, time.UTC) y := gitlab.ISOTime(endDate) milestones[i], _, err = TestGitlabClient.GroupMilestones.CreateGroupMilestone(group.ID, &gitlab.CreateGroupMilestoneOptions{ Title: gitlab.Ptr(fmt.Sprintf("Milestone %d", i)), Description: gitlab.Ptr(fmt.Sprintf("Description %d", i)), StartDate: &x, DueDate: &y, }) if err != nil { t.Fatalf("Could not create test milestones: %v", err) } } return milestones } func CreateDeployKey(t *testing.T, projectID int, options *gitlab.AddDeployKeyOptions) *gitlab.ProjectDeployKey { deployKey, _, err := TestGitlabClient.DeployKeys.AddDeployKey(projectID, options) if err != nil { t.Fatal(err) } t.Cleanup(func() { if _, err := TestGitlabClient.DeployKeys.DeleteDeployKey(projectID, deployKey.ID); err != nil { t.Fatal(err) } }) return deployKey } // CreateProjectEnvironment is a test helper function for creating a project environment func CreateProjectEnvironment(t *testing.T, projectID int, options *gitlab.CreateEnvironmentOptions) *gitlab.Environment { t.Helper() projectEnvironment, _, err := TestGitlabClient.Environments.CreateEnvironment(projectID, options) if err != nil { t.Fatal(err) } t.Cleanup(func() { if projectEnvironment.State != "stopped" { _, _, err = TestGitlabClient.Environments.StopEnvironment(projectID, projectEnvironment.ID, nil) if err != nil { t.Fatal(err) } } if _, err := TestGitlabClient.Environments.DeleteEnvironment(projectID, projectEnvironment.ID); err != nil { t.Fatal(err) } }) return projectEnvironment } func CreateProjectVariable(t *testing.T, projectID int) *gitlab.ProjectVariable { variable, _, err := TestGitlabClient.ProjectVariables.CreateVariable(projectID, &gitlab.CreateProjectVariableOptions{ Key: gitlab.Ptr(fmt.Sprintf("test_key_%d", acctest.RandInt())), Value: gitlab.Ptr("test_value"), }) if err != nil { t.Fatal(err) } t.Cleanup(func() { if _, err := TestGitlabClient.ProjectVariables.RemoveVariable(projectID, variable.Key, nil); err != nil { t.Fatal(err) } }) return variable } func CreateGroupVariable(t *testing.T, groupID int) *gitlab.GroupVariable { variable, _, err := TestGitlabClient.GroupVariables.CreateVariable(groupID, &gitlab.CreateGroupVariableOptions{ Key: gitlab.Ptr(fmt.Sprintf("test_key_%d", acctest.RandInt())), Value: gitlab.Ptr("test_value"), }) if err != nil { t.Fatal(err) } t.Cleanup(func() { if _, err := TestGitlabClient.GroupVariables.RemoveVariable(groupID, variable.Key, nil); err != nil { t.Fatal(err) } }) return variable } func CreateInstanceVariable(t *testing.T) *gitlab.InstanceVariable { variable, _, err := TestGitlabClient.InstanceVariables.CreateVariable(&gitlab.CreateInstanceVariableOptions{ Key: gitlab.Ptr(fmt.Sprintf("test_key_%d", acctest.RandInt())), Value: gitlab.Ptr("test_value"), Description: gitlab.Ptr("test_description"), }) if err != nil { t.Fatal(err) } t.Cleanup(func() { if _, err := TestGitlabClient.InstanceVariables.RemoveVariable(variable.Key); err != nil { t.Fatal(err) } }) return variable } func CreateProjectFile(t *testing.T, projectID int, fileContent string, filePath string, branch string) *gitlab.FileInfo { file, _, err := TestGitlabClient.RepositoryFiles.CreateFile(projectID, filePath, &gitlab.CreateFileOptions{ Branch: &branch, Encoding: gitlab.Ptr("base64"), Content: &fileContent, CommitMessage: gitlab.Ptr(fmt.Sprintf("Random_Commit_Message_%d", acctest.RandInt())), }) if err != nil { t.Fatal(err) } t.Cleanup(func() { if _, err := TestGitlabClient.RepositoryFiles.DeleteFile(projectID, filePath, &gitlab.DeleteFileOptions{ Branch: &branch, CommitMessage: gitlab.Ptr(fmt.Sprintf("Delete_Random_Commit_Message_%d", acctest.RandInt())), }); err != nil { t.Fatal(err) } }) return file } func CreateProjectFilePlaintext(t *testing.T, projectID int, fileContent string, filePath string, branch string) *gitlab.FileInfo { file, _, err := TestGitlabClient.RepositoryFiles.CreateFile(projectID, filePath, &gitlab.CreateFileOptions{ Branch: &branch, Encoding: gitlab.Ptr("text"), Content: &fileContent, CommitMessage: gitlab.Ptr(fmt.Sprintf("Random_Commit_Message_%d", acctest.RandInt())), }) if err != nil { t.Fatal(err) } t.Cleanup(func() { if _, err := TestGitlabClient.RepositoryFiles.DeleteFile(projectID, filePath, &gitlab.DeleteFileOptions{ Branch: &branch, CommitMessage: gitlab.Ptr(fmt.Sprintf("Delete_Random_Commit_Message_%d", acctest.RandInt())), }); err != nil { t.Fatal(err) } }) return file } func CopyFile(src, dst string) error { in, err := os.Open(src) if err != nil { return err } defer in.Close() out, err := os.Create(dst) if err != nil { return err } defer out.Close() _, err = io.Copy(out, in) if err != nil { return err } return out.Close() } func CreateComplianceFramework(t *testing.T, group *gitlab.Group) *api.GraphQLComplianceFramework { t.Helper() query := gitlab.GraphQLQuery{ Query: fmt.Sprintf(` mutation { createComplianceFramework( input: { params: { name: "Compliance Framework %d", description: "Test Compliance Framework", color: "#042", default: false }, namespacePath: "%s" } ) { framework { id, name, description, color, default, pipelineConfigurationFullPath } errors } }`, acctest.RandInt(), group.FullPath), } type createComplianceFrameworkResponse struct { Data struct { CreateComplianceFramework struct { Framework api.GraphQLComplianceFramework `json:"framework"` } `json:"createComplianceFramework"` } `json:"data"` } var response createComplianceFrameworkResponse if _, err := TestGitlabClient.GraphQL.Do(context.Background(), query, &response); err != nil { t.Fatalf("Unable to create compliance framework: %s", err.Error()) } t.Cleanup(func() { query := gitlab.GraphQLQuery{ Query: fmt.Sprintf(` mutation { destroyComplianceFramework( input: { id: "%s" } ) { errors } }`, response.Data.CreateComplianceFramework.Framework.ID), } if _, err := TestGitlabClient.GraphQL.Do(context.Background(), query, nil); err != nil { t.Fatalf("Unable to delete compliance framework: %s", err.Error()) } }) return &response.Data.CreateComplianceFramework.Framework } func DeleteProjectComplianceFrameworks(t *testing.T, project *gitlab.Project) { t.Helper() query := gitlab.GraphQLQuery{ Query: fmt.Sprintf(` mutation { projectUpdateComplianceFrameworks( input: { projectId: "gid://gitlab/Project/%d", complianceFrameworkIds: [] } ) { project { id, complianceFrameworks { nodes { id } } } errors } }`, project.ID), } if _, err := TestGitlabClient.GraphQL.Do(context.Background(), query, nil); err != nil { t.Fatalf("Unable to delete project compliance frameworks: %s", err.Error()) } } func CreateScheduledPipeline(t *testing.T, project int, branch string) (*gitlab.PipelineSchedule, error) { t.Helper() // check if the branch is a full ref value, otherwise add "refs/heads/" to the front if !strings.HasPrefix(branch, "refs/heads") { branch = fmt.Sprintf("refs/heads/%s", branch) } var pipeline *gitlab.PipelineSchedule pipeline, _, err := TestGitlabClient.PipelineSchedules.CreatePipelineSchedule(project, &gitlab.CreatePipelineScheduleOptions{ Description: gitlab.Ptr("test"), Ref: gitlab.Ptr(branch), Cron: gitlab.Ptr("0 1 * * *"), CronTimezone: gitlab.Ptr("UTC"), }) t.Cleanup(func() { _, _ = TestGitlabClient.PipelineSchedules.DeletePipelineSchedule(project, pipeline.ID) }) return pipeline, err } // Function for easily calculating the expiry days from the current time. func GetCurrentTimePlusDays(t *testing.T, days int) gitlab.ISOTime { now := time.Now() expiryDate := now.AddDate(0, 0, days) return gitlab.ISOTime(expiryDate) } // Function for easily calculating a new timestamp value from the current time. func GetCurrentTimestampPlusDays(t *testing.T, days int) time.Time { now := time.Now() return now.AddDate(0, 0, days) } // CreateGroupServiceAccounts is a test helper for creating a specified number of service accounts. func CreateGroupServiceAccounts(t *testing.T, n int, groupID string) []*gitlab.GroupServiceAccount { return CreateGroupServiceAccountsWithPrefix(t, n, groupID, "acctest-service-account") } func CreateGroupServiceAccountsWithPrefix(t *testing.T, n int, groupID, prefix string) []*gitlab.GroupServiceAccount { t.Helper() serviceAccounts := make([]*gitlab.GroupServiceAccount, n) for i := range serviceAccounts { var err error name := acctest.RandomWithPrefix(prefix) username := acctest.RandomWithPrefix(prefix) serviceAccounts[i], _, err = TestGitlabClient.Groups.CreateServiceAccount(groupID, &gitlab.CreateServiceAccountOptions{ Name: gitlab.Ptr(name), Username: gitlab.Ptr(username), }) if err != nil { t.Fatalf("could not create test service account (group_id=%q username=%q): %v", groupID, username, err) } serviceAccountID := serviceAccounts[i].ID // Needed for closure. t.Cleanup(func() { if _, err := TestGitlabClient.Groups.DeleteServiceAccount(groupID, serviceAccountID, nil); err != nil { t.Fatalf("could not cleanup test service account: %v", err) } }) } return serviceAccounts } func CreateInstanceServiceAccounts(t *testing.T, n int) []*gitlab.User { return CreateInstanceServiceAccountsWithPrefix(t, n, "acctest-service-account") } func CreateInstanceServiceAccountsWithPrefix(t *testing.T, n int, prefix string) []*gitlab.User { t.Helper() serviceAccounts := make([]*gitlab.User, n) for i := range serviceAccounts { var err error name := acctest.RandomWithPrefix(prefix) username := acctest.RandomWithPrefix(prefix) serviceAccounts[i], _, err = TestGitlabClient.Users.CreateServiceAccountUser(&gitlab.CreateServiceAccountUserOptions{ Name: gitlab.Ptr(name), Username: gitlab.Ptr(username), }) if err != nil { t.Fatalf("could not create test service account (username=%q): %v", username, err) } serviceAccountID := serviceAccounts[i].ID // Needed for closure. t.Cleanup(func() { if _, err := TestGitlabClient.Users.DeleteUser(serviceAccountID); err != nil { t.Fatalf("could not cleanup test service account: %v", err) } }) } return serviceAccounts } // CreateRunnerWithOptions is a test helper for creating a Runner given some options func CreateRunnerWithOptions(t *testing.T, opts *gitlab.CreateUserRunnerOptions) *gitlab.UserRunner { t.Helper() runner, _, err := TestGitlabClient.Users.CreateUserRunner(opts) if err != nil { t.Fatalf("could not create runner: %v", err) } t.Cleanup(func() { if _, err := TestGitlabClient.Runners.DeleteRegisteredRunnerByID(runner.ID); err != nil { t.Fatalf("could not cleanup runner: %v", err) } }) return runner } func CreateGroupAccessToken(t *testing.T, groupID int) *gitlab.GroupAccessToken { groupAccessToken, _, err := TestGitlabClient.GroupAccessTokens.CreateGroupAccessToken(groupID, &gitlab.CreateGroupAccessTokenOptions{ Name: gitlab.Ptr(fmt.Sprintf("acctest-%d", acctest.RandInt())), Scopes: gitlab.Ptr([]string{"read_api", "read_repository"}), AccessLevel: gitlab.Ptr(gitlab.DeveloperPermissions), ExpiresAt: gitlab.Ptr(gitlab.ISOTime(time.Now().AddDate(0, 0, 7))), }) if err != nil { t.Fatal(err) } t.Cleanup(func() { if _, err := TestGitlabClient.GroupAccessTokens.RevokeGroupAccessToken(groupID, groupAccessToken.ID); err != nil { t.Fatal(err) } }) return groupAccessToken } func CreateCustomInstanceRole(t *testing.T, input *gitlab.CreateMemberRoleOptions) *gitlab.MemberRole { role, _, err := TestGitlabClient.MemberRolesService.CreateInstanceMemberRole(input) if err != nil { t.Fatal(err) } t.Cleanup(func() { _, err = TestGitlabClient.MemberRolesService.DeleteInstanceMemberRole(role.ID) if err != nil { t.Fatalf("Failed to destroy custom role: %v", err) } }) return role }