diff --git a/README.md b/README.md index 8a2c72c031..3f2228bd60 100644 --- a/README.md +++ b/README.md @@ -84,18 +84,22 @@ testing. It will need to have the following scopes selected: Once the token has been created, it must be exported in your environment as `GITHUB_TOKEN`. ### GitHub organization -If you do not have an organization already that you are comfortable running tests again, you will need to [create one](https://help.github.com/en/articles/creating-a-new-organization-from-scratch). The free "Team for Open Source" org type is fine for these tests. The name of the +If you do not have an organization already that you are comfortable running tests against, you will need to [create one](https://help.github.com/en/articles/creating-a-new-organization-from-scratch). The free "Team for Open Source" org type is fine for these tests. The name of the organization must then be exported in your environment as `GITHUB_ORGANIZATION`. -### Test repository -In the organization you are using above, create a test repository named `test-repo`. Make sure the repository is configured as follows: -* The description should be `Test description, used in GitHub Terraform provider acceptance test.` -* The website url should be `http://www.example.com` -* Create two topics within the repo named `test-topic` and `second-test-topic` -* In the repo settings, make sure all features and merge button options are enabled. +### Test repositories +In the organization you are using above, create the following test repositories: + +* `test-repo` + * The description should be `Test description, used in GitHub Terraform provider acceptance test.` + * The website url should be `http://www.example.com` + * Create two topics within the repo named `test-topic` and `second-test-topic` + * In the repo settings, make sure all features and merge button options are enabled. +* `test-repo-template` + * Configure the repository to be a [Template repository](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-template-repository) ### GitHub users Export your github username (the one you used to create the personal access token above) as `GITHUB_TEST_USER`. You will need to export a different github username as `GITHUB_TEST_COLLABORATOR`. Please note that these usernames cannot be the same as each other, and both of them must be real github usernames. The collaborator user does not need to be added as a collaborator to your test repo or organization, but as -the acceptance tests do real things (and will trigger some notifications for this user), you should probably make sure the person you specify knows that you're doing this just to be nice. \ No newline at end of file +the acceptance tests do real things (and will trigger some notifications for this user), you should probably make sure the person you specify knows that you're doing this just to be nice. diff --git a/github/provider_test.go b/github/provider_test.go index 899484eb3f..b97f87f646 100644 --- a/github/provider_test.go +++ b/github/provider_test.go @@ -64,6 +64,9 @@ func testAccPreCheck(t *testing.T) { if v := os.Getenv("GITHUB_TEST_COLLABORATOR"); v == "" { t.Fatal("GITHUB_TEST_COLLABORATOR must be set for acceptance tests") } + if v := os.Getenv("GITHUB_TEMPLATE_REPOSITORY"); v == "" { + t.Fatal("GITHUB_TEMPLATE_REPOSITORY must be set for acceptance tests") + } } func TestProvider_individual(t *testing.T) { diff --git a/github/resource_github_repository.go b/github/resource_github_repository.go index 3d13d6e97f..a0a9929d29 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,24 @@ func resourceGithubRepository() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "template": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "owner": { + Type: schema.TypeString, + Required: true, + }, + "repository": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, }, } } @@ -174,20 +193,55 @@ 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{}) + + 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 +304,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..3ca4fabda7 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,37 @@ resource "github_repository" "foo" { `, randString, randString) } +func testAccGithubRepositoryCreateFromTemplate(randString string) string { + + owner := os.Getenv("GITHUB_ORGANIZATION") + repository := os.Getenv("GITHUB_TEMPLATE_REPOSITORY") + + return fmt.Sprintf(` +resource "github_repository" "foo" { + name = "tf-acc-test-%s" + description = "Terraform acceptance tests %s" + homepage_url = "http://example.com/" + + template { + owner = "%s" + repository = "%s" + } + + # 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, owner, repository) +} + func testAccGithubRepositoryConfigTopics(randString string, topicList string) string { return fmt.Sprintf(` resource "github_repository" "foo" { 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