From d6a569cb07362af8443e8682f08a66417cd3263c Mon Sep 17 00:00:00 2001 From: Jeremy Udit Date: Thu, 19 Dec 2019 13:01:42 -0500 Subject: [PATCH] resource/repository: add create from template --- github/resource_github_repository.go | 84 +++++++++++++++++++++-- github/resource_github_repository_test.go | 69 +++++++++++++++++++ go.mod | 2 + website/docs/r/repository.html.markdown | 15 +++- 4 files changed, 162 insertions(+), 8 deletions(-) diff --git a/github/resource_github_repository.go b/github/resource_github_repository.go index 3d13d6e97f..107f930961 100644 --- a/github/resource_github_repository.go +++ b/github/resource_github_repository.go @@ -2,6 +2,7 @@ package github import ( "context" + "errors" "fmt" "log" "net/http" @@ -137,6 +138,25 @@ func resourceGithubRepository() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "template": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "owner": { + Type: schema.TypeString, + Optional: true, + Default: false, + }, + "repository": { + Type: schema.TypeString, + Optional: true, + Default: false, + }, + }, + }, + }, }, } } @@ -174,20 +194,58 @@ func resourceGithubRepositoryCreate(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("Cannot set the default branch on a new repository to something other than 'master'.") } - orgName := meta.(*Organization).name repoReq := resourceGithubRepositoryObject(d) + orgName := meta.(*Organization).name + repoName := repoReq.GetName() ctx := context.Background() - log.Printf("[DEBUG] Creating repository: %s/%s", orgName, repoReq.GetName()) - repo, _, err := client.Repositories.Create(ctx, orgName, repoReq) - if err != nil { - return err + log.Printf("[DEBUG] Creating repository: %s/%s", orgName, repoName) + + if template, ok := d.GetOk("template"); ok { + templateConfigBlocks := template.([]interface{}) + if len(templateConfigBlocks) > 1 { + return errors.New("cannot specify template more than once") + } + + for _, templateConfigBlock := range templateConfigBlocks { + templateConfigMap, ok := templateConfigBlock.(map[string]interface{}) + if !ok { + return errors.New("failed to unpack template configuration block") + } + + templateRepo := templateConfigMap["repository"].(string) + templateRepoOwner := templateConfigMap["owner"].(string) + templateRepoReq := github.TemplateRepoRequest{ + Name: &repoName, + Owner: &orgName, + Description: github.String(d.Get("description").(string)), + Private: github.Bool(d.Get("private").(bool)), + } + + repo, _, err := client.Repositories.CreateFromTemplate(ctx, + templateRepoOwner, + templateRepo, + &templateRepoReq, + ) + + if err != nil { + return err + } + + d.SetId(*repo.Name) + } + } else { + // Create without a repository template + repo, _, err := client.Repositories.Create(ctx, orgName, repoReq) + if err != nil { + return err + } + d.SetId(*repo.Name) } - d.SetId(*repo.Name) topics := repoReq.Topics if len(topics) > 0 { - _, _, err = client.Repositories.ReplaceAllTopics(ctx, orgName, repoReq.GetName(), topics) + _, _, err = client.Repositories.ReplaceAllTopics(ctx, orgName, repoName, topics) if err != nil { return err } @@ -250,6 +308,18 @@ func resourceGithubRepositoryRead(d *schema.ResourceData, meta interface{}) erro d.Set("http_clone_url", repo.CloneURL) d.Set("archived", repo.Archived) d.Set("topics", flattenStringList(repo.Topics)) + + if repo.TemplateRepository != nil { + d.Set("template", []interface{}{ + map[string]interface{}{ + "owner": repo.TemplateRepository.Owner.Login, + "repository": repo.TemplateRepository.Name, + }, + }) + } else { + d.Set("template", []interface{}{}) + } + return nil } diff --git a/github/resource_github_repository_test.go b/github/resource_github_repository_test.go index b18e8926cd..622f8a172e 100644 --- a/github/resource_github_repository_test.go +++ b/github/resource_github_repository_test.go @@ -490,6 +490,36 @@ func TestAccGithubRepository_autoInitForceNew(t *testing.T) { }) } +func TestAccGithubRepository_createFromTemplate(t *testing.T) { + var repo github.Repository + + rn := "github_repository.foo" + randString := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGithubRepositoryDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGithubRepositoryCreateFromTemplate(randString), + Check: resource.ComposeTestCheckFunc( + testAccCheckGithubRepositoryExists(rn, &repo), + testAccCheckGithubRepositoryTemplateRepoAttribute(rn, &repo), + ), + }, + { + ResourceName: rn, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "auto_init", + }, + }, + }, + }) +} + func testAccCheckGithubRepositoryExists(n string, repo *github.Repository) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -513,6 +543,17 @@ func testAccCheckGithubRepositoryExists(n string, repo *github.Repository) resou } } +func testAccCheckGithubRepositoryTemplateRepoAttribute(n string, repo *github.Repository) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if *repo.TemplateRepository.IsTemplate != true { + return fmt.Errorf("got repo %q; want %q", *repo.TemplateRepository, repo) + } + + return nil + } +} + type testAccGithubRepositoryExpectedAttributes struct { Name string Description string @@ -841,6 +882,34 @@ resource "github_repository" "foo" { `, randString, randString) } +func testAccGithubRepositoryCreateFromTemplate(randString string) string { + return fmt.Sprintf(` +resource "github_repository" "foo" { + name = "tf-acc-test-%s" + description = "Terraform acceptance tests %s" + homepage_url = "http://example.com/" + + template { + # FIXME: Change this to something more suitable for CI runs + owner = "jcudit" + repository = "terraform-template-module" + } + + # So that acceptance tests can be run in a github organization + # with no billing + private = false + + has_issues = true + has_wiki = true + allow_merge_commit = true + allow_squash_merge = false + allow_rebase_merge = false + has_downloads = true + +} +`, randString, randString) +} + func testAccGithubRepositoryConfigTopics(randString string, topicList string) string { return fmt.Sprintf(` resource "github_repository" "foo" { diff --git a/go.mod b/go.mod index 10eb37901d..c7b3b44a52 100644 --- a/go.mod +++ b/go.mod @@ -7,3 +7,5 @@ require ( github.com/terraform-providers/terraform-provider-tls v1.2.0 golang.org/x/oauth2 v0.0.0-20190604054615-0f29369cfe45 ) + +go 1.13 diff --git a/website/docs/r/repository.html.markdown b/website/docs/r/repository.html.markdown index 28723258b5..5447db65be 100644 --- a/website/docs/r/repository.html.markdown +++ b/website/docs/r/repository.html.markdown @@ -21,6 +21,11 @@ resource "github_repository" "example" { description = "My awesome codebase" private = true + + template { + owner = "github" + repo = "terraform-module-template" + } } ``` @@ -65,10 +70,18 @@ and after a correct reference has been created for the target branch inside the initial repository creation and create the target branch inside of the repository prior to setting this attribute. * `archived` - (Optional) Specifies if the repository should be archived. Defaults to `false`. +~> **NOTE** Currently, the API does not support unarchiving. * `topics` - (Optional) The list of topics of the repository. -~> **NOTE** Currently, the API does not support unarchiving. +* `template` - (Optional) Use a template repository to create this resource. See [Template Repositories](#template-repositories) below for details. + +### Template Repositories + +`template` supports the following arguments: + +* `owner`: The GitHub organization or user the template repository is owned by. +* `repository`: The name of the template repository. ## Attributes Reference