From 4bdcb8118c54086f04070fb2cda563026779db6b Mon Sep 17 00:00:00 2001 From: Alan Goldman Date: Mon, 31 Mar 2025 17:54:30 -0400 Subject: [PATCH 1/2] Adding a computed attribute for the repository owner in the `github_repository` resource and data source. Fixes #2503 --- github/data_source_github_repository.go | 12 +- github/data_source_github_repository_test.go | 175 +++++++++++++++++++ github/resource_github_repository.go | 6 + github/resource_github_repository_test.go | 43 +++++ website/docs/d/repository.html.markdown | 2 + 5 files changed, 237 insertions(+), 1 deletion(-) diff --git a/github/data_source_github_repository.go b/github/data_source_github_repository.go index 94e40d0c3b..40ceecb9fa 100644 --- a/github/data_source_github_repository.go +++ b/github/data_source_github_repository.go @@ -21,7 +21,7 @@ func dataSourceGithubRepository() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, - ConflictsWith: []string{"name"}, + ConflictsWith: []string{"name", "owner"}, }, "name": { Type: schema.TypeString, @@ -29,6 +29,13 @@ func dataSourceGithubRepository() *schema.Resource { Computed: true, ConflictsWith: []string{"full_name"}, }, + "owner": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"full_name"}, + Description: "Owner of the repository. If not provided, the owner specified in the provider configuration will be used.", + }, "description": { Type: schema.TypeString, Default: nil, @@ -354,6 +361,9 @@ func dataSourceGithubRepositoryRead(d *schema.ResourceData, meta any) error { if name, ok := d.GetOk("name"); ok { repoName = name.(string) } + if ownerName, ok := d.GetOk("owner"); ok { + owner = ownerName.(string) + } if repoName == "" { return fmt.Errorf("one of %q or %q has to be provided", "full_name", "name") diff --git a/github/data_source_github_repository_test.go b/github/data_source_github_repository_test.go index 6c51df8062..aca100dae8 100644 --- a/github/data_source_github_repository_test.go +++ b/github/data_source_github_repository_test.go @@ -428,4 +428,179 @@ EOT testCase(t, organization) }) }) + + t.Run("queries a repository using owner and name", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + + config := fmt.Sprintf(` + resource "github_repository" "test" { + name = "tf-acc-%s" + } + + data "github_repository" "test" { + name = github_repository.test.name + owner = "%s" + } + `, randomID, testOrganization) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.github_repository.test", "owner", + testOrganization, + ), + ) + + testCase := func(t *testing.T, mode string) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, mode) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + }) + } + + t.Run("with an anonymous account", func(t *testing.T) { + testCase(t, anonymous) + }) + + t.Run("with an individual account", func(t *testing.T) { + testCase(t, individual) + }) + t.Run("with an organization account", func(t *testing.T) { + testCase(t, organization) + }) + }) + + t.Run("validates conflicts between full_name, name, and owner", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + + config := fmt.Sprintf(` + resource "github_repository" "test" { + name = "tf-acc-%[1]s" + vulnerability_alerts = true + } + `, randomID) + + // Test invalid combinations + invalidConfigs := []string{ + // full_name with name + fmt.Sprintf(` + resource "github_repository" "test" { + name = "tf-acc-%[1]s" + vulnerability_alerts = true + } + + data "github_repository" "test" { + full_name = "%[2]s/tf-acc-%[1]s" + name = "tf-acc-%[1]s" + } + `, randomID, testOrganization), + // full_name with owner + fmt.Sprintf(` + resource "github_repository" "test" { + name = "tf-acc-%[1]s" + } + + data "github_repository" "test" { + full_name = "%[2]s/tf-acc-%[1]s" + owner = "%[2]s" + } + `, randomID, testOrganization), + // full_name with both name and owner + fmt.Sprintf(` + resource "github_repository" "test" { + name = "tf-acc-%[1]s" + } + + data "github_repository" "test" { + full_name = "%[2]s/tf-acc-%[1]s" + name = "tf-acc-%[1]s" + owner = "%[2]s" + } + `, randomID, testOrganization), + } + + // Test valid combinations + validConfigs := []string{ + // Just full_name + fmt.Sprintf(` + resource "github_repository" "test" { + name = "tf-acc-%[1]s" + } + + data "github_repository" "test" { + full_name = "%[2]s/tf-acc-%[1]s" + } + `, randomID, testOrganization), + // Just name (uses provider owner) + fmt.Sprintf(` + resource "github_repository" "test" { + name = "tf-acc-%[1]s" + } + + data "github_repository" "test" { + name = "tf-acc-%[1]s" + } + `, randomID), + // name with owner + fmt.Sprintf(` + resource "github_repository" "test" { + name = "tf-acc-%[1]s" + } + + data "github_repository" "test" { + name = "tf-acc-%[1]s" + owner = "%[2]s" + } + `, randomID, testOrganization), + } + + testCase := func(t *testing.T, mode string) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, mode) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create the repository first + { + Config: config, + }, + // Test that invalid configs fail + { + Config: invalidConfigs[0], + ExpectError: regexp.MustCompile("(?i)conflicts with"), + }, + { + Config: invalidConfigs[1], + ExpectError: regexp.MustCompile("(?i)conflicts with"), + }, + { + Config: invalidConfigs[2], + ExpectError: regexp.MustCompile("(?i)conflicts with"), + }, + // Test that valid configs succeed + { + Config: validConfigs[0], + }, + { + Config: validConfigs[1], + }, + { + Config: validConfigs[2], + }, + }, + }) + } + + t.Run("with an individual account", func(t *testing.T) { + testCase(t, individual) + }) + + t.Run("with an organization account", func(t *testing.T) { + testCase(t, organization) + }) + }) } diff --git a/github/resource_github_repository.go b/github/resource_github_repository.go index 6f4abaeb62..9aa5ca62ea 100644 --- a/github/resource_github_repository.go +++ b/github/resource_github_repository.go @@ -402,6 +402,11 @@ func resourceGithubRepository() *schema.Resource { Computed: true, Description: "A string of the form 'orgname/reponame'.", }, + "owner": { + Type: schema.TypeString, + Computed: true, + Description: "The owner of the repository.", + }, "html_url": { Type: schema.TypeString, Computed: true, @@ -833,6 +838,7 @@ func resourceGithubRepositoryRead(d *schema.ResourceData, meta any) error { _ = d.Set("topics", flattenStringList(repo.Topics)) _ = d.Set("node_id", repo.GetNodeID()) _ = d.Set("repo_id", repo.GetID()) + _ = d.Set("owner", repo.GetOwner().GetLogin()) // GitHub API doesn't respond following parameters when repository is archived if !d.Get("archived").(bool) { diff --git a/github/resource_github_repository_test.go b/github/resource_github_repository_test.go index c4fab6f9d8..7d8dde389a 100644 --- a/github/resource_github_repository_test.go +++ b/github/resource_github_repository_test.go @@ -958,6 +958,49 @@ func TestAccGithubRepositories(t *testing.T) { testCase(t, organization) }) }) + + t.Run("creates repository and returns owner field", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + + config := fmt.Sprintf(` + resource "github_repository" "test" { + name = "tf-acc-test-owner-%[1]s" + description = "Terraform acceptance tests %[1]s" + } + `, randomID) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "github_repository.test", "owner", + ), + ) + + testCase := func(t *testing.T, mode string) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, mode) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + }) + } + + t.Run("with an anonymous account", func(t *testing.T) { + t.Skip("anonymous account not supported for this operation") + }) + + t.Run("with an individual account", func(t *testing.T) { + testCase(t, individual) + }) + + t.Run("with an organization account", func(t *testing.T) { + testCase(t, organization) + }) + }) + } func TestAccGithubRepositoryPages(t *testing.T) { diff --git a/website/docs/d/repository.html.markdown b/website/docs/d/repository.html.markdown index 442d1b6d56..83c6d80ffc 100644 --- a/website/docs/d/repository.html.markdown +++ b/website/docs/d/repository.html.markdown @@ -25,6 +25,8 @@ The following arguments are supported: * `full_name` - (Optional) Full name of the repository (in `org/name` format). +* `owner` - (Optional) Owner of the repository. If not provided, the owner specified in the provider configuration will be used. + ## Attributes Reference * `node_id` - the Node ID of the repository. From 0927341baffa88d66f45bc711cd2b8829c81f501 Mon Sep 17 00:00:00 2001 From: Nick Floyd <139819+nickfloyd@users.noreply.github.com> Date: Thu, 4 Dec 2025 10:45:23 -0600 Subject: [PATCH 2/2] Update website/docs/d/repository.html.markdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Diógenes Fernandes --- website/docs/d/repository.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/d/repository.html.markdown b/website/docs/d/repository.html.markdown index 83c6d80ffc..1b8d6fa617 100644 --- a/website/docs/d/repository.html.markdown +++ b/website/docs/d/repository.html.markdown @@ -25,7 +25,7 @@ The following arguments are supported: * `full_name` - (Optional) Full name of the repository (in `org/name` format). -* `owner` - (Optional) Owner of the repository. If not provided, the owner specified in the provider configuration will be used. +* `owner` - (Optional) Owner of the repository. If not provided, the provider's default owner is used. ## Attributes Reference