From 0e0a69e18038da5dcbda874eb37b6028ff7fe071 Mon Sep 17 00:00:00 2001 From: Ahmet DEMIR Date: Mon, 21 Sep 2020 16:23:29 +0200 Subject: [PATCH] Adding support for project templates * template_name * template_project_id * use_custom_template * group_with_project_templates_id --- docs/resources/project.md | 8 ++ gitlab/resource_gitlab_project.go | 36 ++++++ gitlab/resource_gitlab_project_test.go | 148 ++++++++++++++++++++++++- scripts/start-gitlab.sh | 28 ++++- 4 files changed, 216 insertions(+), 4 deletions(-) diff --git a/docs/resources/project.md b/docs/resources/project.md index 758a3a7e5..05260c55f 100644 --- a/docs/resources/project.md +++ b/docs/resources/project.md @@ -82,6 +82,14 @@ The following arguments are supported: * `push_rules` (Optional) Push rules for the project (documented below). +* `template_name` - (Optional) When used without use_custom_template, name of a built-in project template. When used with use_custom_template, name of a custom project template. This option is mutually exclusive with `template_project_id`. + +* `template_project_id` - (Optional) When used with use_custom_template, project ID of a custom project template. This is preferable to using template_name since template_name may be ambiguous (enterprise edition). This option is mutually exclusive with `template_name`. + +* `use_custom_template` - (Optional) Use either custom instance or group (with group_with_project_templates_id) project template (enterprise edition). + +* `group_with_project_templates_id` - (Optional) For group-level custom templates, specifies ID of group from which all the custom project templates are sourced. Leave empty for instance-level templates. Requires use_custom_template to be true (enterprise edition). + ## Attributes Reference The following additional attributes are exported: diff --git a/gitlab/resource_gitlab_project.go b/gitlab/resource_gitlab_project.go index 9acf6d4ec..101f84d5e 100644 --- a/gitlab/resource_gitlab_project.go +++ b/gitlab/resource_gitlab_project.go @@ -248,6 +248,26 @@ var resourceGitLabProjectSchema = map[string]*schema.Schema{ }, }, }, + "template_name": { + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"template_project_id"}, + ForceNew: true, + }, + "template_project_id": { + Type: schema.TypeInt, + Optional: true, + ConflictsWith: []string{"template_name"}, + ForceNew: true, + }, + "use_custom_template": { + Type: schema.TypeBool, + Optional: true, + }, + "group_with_project_templates_id": { + Type: schema.TypeInt, + Optional: true, + }, } func resourceGitlabProject() *schema.Resource { @@ -342,6 +362,22 @@ func resourceGitlabProjectCreate(d *schema.ResourceData, meta interface{}) error options.ImportURL = gitlab.String(v.(string)) } + if v, ok := d.GetOk("template_name"); ok { + options.TemplateName = gitlab.String(v.(string)) + } + + if v, ok := d.GetOk("template_project_id"); ok { + options.TemplateProjectID = gitlab.Int(v.(int)) + } + + if v, ok := d.GetOk("use_custom_template"); ok { + options.UseCustomTemplate = gitlab.Bool(v.(bool)) + } + + if v, ok := d.GetOk("group_with_project_templates_id"); ok { + options.GroupWithProjectTemplatesID = gitlab.Int(v.(int)) + } + log.Printf("[DEBUG] create gitlab project %q", *options.Name) project, _, err := client.Projects.CreateProject(options) diff --git a/gitlab/resource_gitlab_project_test.go b/gitlab/resource_gitlab_project_test.go index c2af7b310..cac58a7f5 100644 --- a/gitlab/resource_gitlab_project_test.go +++ b/gitlab/resource_gitlab_project_test.go @@ -203,6 +203,74 @@ max_file_size = 123 Config: testAccGitlabProjectConfigPushRules(rInt, `author_email_regex = "foo_author"`), ExpectError: regexp.MustCompile(regexp.QuoteMeta("Project push rules are not supported in your version of GitLab")), }, + // Create a project using template name + { + Config: testAccGitlabProjectConfigTemplateName(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckGitlabProjectExists("gitlab_project.template-name", &received), + testAccCheckGitlabProjectDefaultBranch(&received, &testAccGitlabProjectExpectedAttributes{ + DefaultBranch: "master", + }), + func(state *terraform.State) error { + client := testAccProvider.Meta().(*gitlab.Client) + + projectID := state.RootModule().Resources["gitlab_project.template-name"].Primary.ID + + _, _, err := client.RepositoryFiles.GetFile(projectID, ".ruby-version", &gitlab.GetFileOptions{Ref: gitlab.String("master")}, nil) + if err != nil { + return fmt.Errorf("failed to get '.ruby-version' file from template project: %w", err) + } + + return nil + }, + ), + }, + // Create a project using custom template name + { + Config: testAccGitlabProjectConfigTemplateNameCustom(rInt), + SkipFunc: isRunningInCE, + Check: resource.ComposeTestCheckFunc( + testAccCheckGitlabProjectExists("gitlab_project.template-name-custom", &received), + testAccCheckGitlabProjectDefaultBranch(&received, &testAccGitlabProjectExpectedAttributes{ + DefaultBranch: "master", + }), + func(state *terraform.State) error { + client := testAccProvider.Meta().(*gitlab.Client) + + projectID := state.RootModule().Resources["gitlab_project.template-name-custom"].Primary.ID + + _, _, err := client.RepositoryFiles.GetFile(projectID, "Gemfile", &gitlab.GetFileOptions{Ref: gitlab.String("master")}, nil) + if err != nil { + return fmt.Errorf("failed to get 'Gemfile' file from template project: %w", err) + } + + return nil + }, + ), + }, + // Create a project using custom template project id + { + Config: testAccGitlabProjectConfigTemplateProjectID(rInt), + SkipFunc: isRunningInCE, + Check: resource.ComposeTestCheckFunc( + testAccCheckGitlabProjectExists("gitlab_project.template-id", &received), + testAccCheckGitlabProjectDefaultBranch(&received, &testAccGitlabProjectExpectedAttributes{ + DefaultBranch: "master", + }), + func(state *terraform.State) error { + client := testAccProvider.Meta().(*gitlab.Client) + + projectID := state.RootModule().Resources["gitlab_project.template-id"].Primary.ID + + _, _, err := client.RepositoryFiles.GetFile(projectID, "Rakefile", &gitlab.GetFileOptions{Ref: gitlab.String("master")}, nil) + if err != nil { + return fmt.Errorf("failed to get 'Rakefile' file from template project: %w", err) + } + + return nil + }, + ), + }, // Update to original project config { Config: testAccGitlabProjectConfig(rInt), @@ -224,7 +292,7 @@ func TestAccGitlabProject_initializeWithReadme(t *testing.T) { Config: testAccGitlabProjectConfigInitializeWithReadme(rInt), Check: resource.ComposeTestCheckFunc( testAccCheckGitlabProjectExists("gitlab_project.foo", &project), - testAccCheckGitlabProjectInitializeWithReadme(&project, &testAccGitlabProjectExpectedAttributes{ + testAccCheckGitlabProjectDefaultBranch(&project, &testAccGitlabProjectExpectedAttributes{ DefaultBranch: "master", }), ), @@ -437,6 +505,22 @@ func TestAccGitlabProject_importURL(t *testing.T) { }) } +func TestAccGitlabProjec_templateMutualExclusiveNameAndID(t *testing.T) { + rInt := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckMutualExclusiveNameAndID(rInt), + SkipFunc: isRunningInCE, + ExpectError: regexp.MustCompile(regexp.QuoteMeta(`"template_project_id": conflicts with template_name`)), + }, + }, + }) +} + func testAccCheckGitlabProjectExists(n string, project *gitlab.Project) resource.TestCheckFunc { return func(s *terraform.State) error { var err error @@ -506,10 +590,10 @@ func testAccCheckAggregateGitlabProject(expected, received *gitlab.Project) reso return resource.ComposeAggregateTestCheckFunc(checks...) } -func testAccCheckGitlabProjectInitializeWithReadme(project *gitlab.Project, want *testAccGitlabProjectExpectedAttributes) resource.TestCheckFunc { +func testAccCheckGitlabProjectDefaultBranch(project *gitlab.Project, want *testAccGitlabProjectExpectedAttributes) resource.TestCheckFunc { return func(s *terraform.State) error { if project.DefaultBranch != want.DefaultBranch { - return fmt.Errorf("got description %q; want %q", project.DefaultBranch, want.DefaultBranch) + return fmt.Errorf("got default branch %q; want %q", project.DefaultBranch, want.DefaultBranch) } return nil @@ -765,3 +849,61 @@ resource "gitlab_project" "foo" { } `, rInt, pushRules) } + +func testAccGitlabProjectConfigTemplateName(rInt int) string { + return fmt.Sprintf(` +resource "gitlab_project" "template-name" { + name = "template-name-%d" + path = "template-name.%d" + description = "Terraform acceptance tests" + template_name = "rails" + default_branch = "master" +} + `, rInt, rInt) +} + +// 2020-09-07: Currently Gitlab (version 13.3.6 ) doesn't allow in admin API +// ability to set a group as instance level templates. +// To test resource_gitlab_project_test template features we add +// group, project myrails and admin settings directly in scripts/start-gitlab.sh +// Once Gitlab add admin template in API we could manage group/project/settings +// directly in tests like TestAccGitlabProject_basic. +func testAccGitlabProjectConfigTemplateNameCustom(rInt int) string { + return fmt.Sprintf(` +resource "gitlab_project" "template-name-custom" { + name = "template-name-custom-%d" + path = "template-name-custom.%d" + description = "Terraform acceptance tests" + template_name = "myrails" + use_custom_template = true + default_branch = "master" +} + `, rInt, rInt) +} + +func testAccGitlabProjectConfigTemplateProjectID(rInt int) string { + return fmt.Sprintf(` +resource "gitlab_project" "template-id" { + name = "template-id-%d" + path = "template-id.%d" + description = "Terraform acceptance tests" + template_project_id = 999 + use_custom_template = true + default_branch = "master" +} + `, rInt, rInt) +} + +func testAccCheckMutualExclusiveNameAndID(rInt int) string { + return fmt.Sprintf(` +resource "gitlab_project" "template-mutual-exclusive" { + name = "template-mutual-exclusive-%d" + path = "template-mutual-exclusive.%d" + description = "Terraform acceptance tests" + template_name = "rails" + template_project_id = 999 + use_custom_template = true + default_branch = "master" +} + `, rInt, rInt) +} diff --git a/scripts/start-gitlab.sh b/scripts/start-gitlab.sh index 06c24027a..8b2a5565e 100755 --- a/scripts/start-gitlab.sh +++ b/scripts/start-gitlab.sh @@ -41,4 +41,30 @@ echo "Creating access token" ) | docker exec -i gitlab gitlab-rails console - +# 2020-09-07: Currently Gitlab (version 13.3.6 ) doesn't allow in admin API +# ability to set a group as instance level templates. +# To test resource_gitlab_project_test template features we add +# group, project myrails and admin settings directly in scripts/start-gitlab.sh +# Once Gitlab add admin template in API we could manage group/project/settings +# directly in tests like TestAccGitlabProject_basic. +# Works on CE too +echo +echo "Creating an instance level template group with a simple template based on rails" +( + echo -n 'group_template = Group.new(' + echo -n 'name: :terraform, ' + echo -n 'path: :terraform);' + echo -n 'group_template.save!;' + echo -n 'application_settings = ApplicationSetting.find_by "";' + echo -n 'application_settings.custom_project_templates_group_id = group_template.id;' + echo -n 'application_settings.save!;' + echo -n 'attrs = {' + echo -n 'name: :myrails, ' + echo -n 'path: :myrails, ' + echo -n 'namespace_id: group_template.id, ' + echo -n 'template_name: :rails, ' + echo -n 'id: 999};' + echo -n 'project = ::Projects::CreateService.new(User.find_by_username("root"), attrs).execute;' + echo -n 'project.saved?;' +) | +docker exec -i gitlab gitlab-rails console