Skip to content

Commit a5802d3

Browse files
committed
Adding a computed attribute for the repository owner in the github_repository resource and data source. Fixes #2503
1 parent 6d7a787 commit a5802d3

5 files changed

Lines changed: 277 additions & 1 deletion

github/data_source_github_repository.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,21 @@ func dataSourceGithubRepository() *schema.Resource {
2121
Type: schema.TypeString,
2222
Optional: true,
2323
Computed: true,
24-
ConflictsWith: []string{"name"},
24+
ConflictsWith: []string{"name", "owner"},
2525
},
2626
"name": {
2727
Type: schema.TypeString,
2828
Optional: true,
2929
Computed: true,
3030
ConflictsWith: []string{"full_name"},
3131
},
32+
"owner": {
33+
Type: schema.TypeString,
34+
Optional: true,
35+
Computed: true,
36+
ConflictsWith: []string{"full_name"},
37+
Description: "Owner of the repository. If not provided, the owner specified in the provider configuration will be used.",
38+
},
3239
"description": {
3340
Type: schema.TypeString,
3441
Default: nil,
@@ -354,6 +361,9 @@ func dataSourceGithubRepositoryRead(d *schema.ResourceData, meta any) error {
354361
if name, ok := d.GetOk("name"); ok {
355362
repoName = name.(string)
356363
}
364+
if ownerName, ok := d.GetOk("owner"); ok {
365+
owner = ownerName.(string)
366+
}
357367

358368
if repoName == "" {
359369
return fmt.Errorf("one of %q or %q has to be provided", "full_name", "name")

github/data_source_github_repository_test.go

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,4 +428,179 @@ EOT
428428
testCase(t, organization)
429429
})
430430
})
431+
432+
t.Run("queries a repository using owner and name", func(t *testing.T) {
433+
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
434+
435+
config := fmt.Sprintf(`
436+
resource "github_repository" "test" {
437+
name = "tf-acc-%s"
438+
}
439+
440+
data "github_repository" "test" {
441+
name = github_repository.test.name
442+
owner = "%s"
443+
}
444+
`, randomID, testOrganization)
445+
446+
check := resource.ComposeTestCheckFunc(
447+
resource.TestCheckResourceAttr(
448+
"data.github_repository.test", "owner",
449+
testOrganization,
450+
),
451+
)
452+
453+
testCase := func(t *testing.T, mode string) {
454+
resource.Test(t, resource.TestCase{
455+
PreCheck: func() { skipUnlessMode(t, mode) },
456+
Providers: testAccProviders,
457+
Steps: []resource.TestStep{
458+
{
459+
Config: config,
460+
Check: check,
461+
},
462+
},
463+
})
464+
}
465+
466+
t.Run("with an anonymous account", func(t *testing.T) {
467+
testCase(t, anonymous)
468+
})
469+
470+
t.Run("with an individual account", func(t *testing.T) {
471+
testCase(t, individual)
472+
})
473+
t.Run("with an organization account", func(t *testing.T) {
474+
testCase(t, organization)
475+
})
476+
})
477+
478+
t.Run("validates conflicts between full_name, name, and owner", func(t *testing.T) {
479+
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
480+
481+
config := fmt.Sprintf(`
482+
resource "github_repository" "test" {
483+
name = "tf-acc-%[1]s"
484+
vulnerability_alerts = true
485+
}
486+
`, randomID)
487+
488+
// Test invalid combinations
489+
invalidConfigs := []string{
490+
// full_name with name
491+
fmt.Sprintf(`
492+
resource "github_repository" "test" {
493+
name = "tf-acc-%[1]s"
494+
vulnerability_alerts = true
495+
}
496+
497+
data "github_repository" "test" {
498+
full_name = "%[2]s/tf-acc-%[1]s"
499+
name = "tf-acc-%[1]s"
500+
}
501+
`, randomID, testOrganization),
502+
// full_name with owner
503+
fmt.Sprintf(`
504+
resource "github_repository" "test" {
505+
name = "tf-acc-%[1]s"
506+
}
507+
508+
data "github_repository" "test" {
509+
full_name = "%[2]s/tf-acc-%[1]s"
510+
owner = "%[2]s"
511+
}
512+
`, randomID, testOrganization),
513+
// full_name with both name and owner
514+
fmt.Sprintf(`
515+
resource "github_repository" "test" {
516+
name = "tf-acc-%[1]s"
517+
}
518+
519+
data "github_repository" "test" {
520+
full_name = "%[2]s/tf-acc-%[1]s"
521+
name = "tf-acc-%[1]s"
522+
owner = "%[2]s"
523+
}
524+
`, randomID, testOrganization),
525+
}
526+
527+
// Test valid combinations
528+
validConfigs := []string{
529+
// Just full_name
530+
fmt.Sprintf(`
531+
resource "github_repository" "test" {
532+
name = "tf-acc-%[1]s"
533+
}
534+
535+
data "github_repository" "test" {
536+
full_name = "%[2]s/tf-acc-%[1]s"
537+
}
538+
`, randomID, testOrganization),
539+
// Just name (uses provider owner)
540+
fmt.Sprintf(`
541+
resource "github_repository" "test" {
542+
name = "tf-acc-%[1]s"
543+
}
544+
545+
data "github_repository" "test" {
546+
name = "tf-acc-%[1]s"
547+
}
548+
`, randomID),
549+
// name with owner
550+
fmt.Sprintf(`
551+
resource "github_repository" "test" {
552+
name = "tf-acc-%[1]s"
553+
}
554+
555+
data "github_repository" "test" {
556+
name = "tf-acc-%[1]s"
557+
owner = "%[2]s"
558+
}
559+
`, randomID, testOrganization),
560+
}
561+
562+
testCase := func(t *testing.T, mode string) {
563+
resource.Test(t, resource.TestCase{
564+
PreCheck: func() { skipUnlessMode(t, mode) },
565+
Providers: testAccProviders,
566+
Steps: []resource.TestStep{
567+
// Create the repository first
568+
{
569+
Config: config,
570+
},
571+
// Test that invalid configs fail
572+
{
573+
Config: invalidConfigs[0],
574+
ExpectError: regexp.MustCompile("(?i)conflicts with"),
575+
},
576+
{
577+
Config: invalidConfigs[1],
578+
ExpectError: regexp.MustCompile("(?i)conflicts with"),
579+
},
580+
{
581+
Config: invalidConfigs[2],
582+
ExpectError: regexp.MustCompile("(?i)conflicts with"),
583+
},
584+
// Test that valid configs succeed
585+
{
586+
Config: validConfigs[0],
587+
},
588+
{
589+
Config: validConfigs[1],
590+
},
591+
{
592+
Config: validConfigs[2],
593+
},
594+
},
595+
})
596+
}
597+
598+
t.Run("with an individual account", func(t *testing.T) {
599+
testCase(t, individual)
600+
})
601+
602+
t.Run("with an organization account", func(t *testing.T) {
603+
testCase(t, organization)
604+
})
605+
})
431606
}

github/resource_github_repository.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,11 @@ func resourceGithubRepository() *schema.Resource {
402402
Computed: true,
403403
Description: "A string of the form 'orgname/reponame'.",
404404
},
405+
"owner": {
406+
Type: schema.TypeString,
407+
Computed: true,
408+
Description: "The owner of the repository.",
409+
},
405410
"html_url": {
406411
Type: schema.TypeString,
407412
Computed: true,
@@ -833,6 +838,9 @@ func resourceGithubRepositoryRead(d *schema.ResourceData, meta any) error {
833838
_ = d.Set("topics", flattenStringList(repo.Topics))
834839
_ = d.Set("node_id", repo.GetNodeID())
835840
_ = d.Set("repo_id", repo.GetID())
841+
if repo.Owner != nil {
842+
_ = d.Set("owner", repo.Owner.GetLogin())
843+
}
836844

837845
// GitHub API doesn't respond following parameters when repository is archived
838846
if !d.Get("archived").(bool) {

github/resource_github_repository_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,49 @@ func TestAccGithubRepositories(t *testing.T) {
958958
testCase(t, organization)
959959
})
960960
})
961+
962+
t.Run("creates repository and returns owner field", func(t *testing.T) {
963+
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
964+
965+
config := fmt.Sprintf(`
966+
resource "github_repository" "test" {
967+
name = "tf-acc-test-owner-%[1]s"
968+
description = "Terraform acceptance tests %[1]s"
969+
}
970+
`, randomID)
971+
972+
check := resource.ComposeTestCheckFunc(
973+
resource.TestCheckResourceAttrSet(
974+
"github_repository.test", "owner",
975+
),
976+
)
977+
978+
testCase := func(t *testing.T, mode string) {
979+
resource.Test(t, resource.TestCase{
980+
PreCheck: func() { skipUnlessMode(t, mode) },
981+
Providers: testAccProviders,
982+
Steps: []resource.TestStep{
983+
{
984+
Config: config,
985+
Check: check,
986+
},
987+
},
988+
})
989+
}
990+
991+
t.Run("with an anonymous account", func(t *testing.T) {
992+
t.Skip("anonymous account not supported for this operation")
993+
})
994+
995+
t.Run("with an individual account", func(t *testing.T) {
996+
testCase(t, individual)
997+
})
998+
999+
t.Run("with an organization account", func(t *testing.T) {
1000+
testCase(t, organization)
1001+
})
1002+
})
1003+
9611004
}
9621005

9631006
func TestAccGithubRepositoryPages(t *testing.T) {
@@ -2025,6 +2068,44 @@ func TestAccGithubRepository_fork(t *testing.T) {
20252068
})
20262069
}
20272070

2071+
func TestGithubRepositoryOwnerNilHandling(t *testing.T) {
2072+
d := schema.TestResourceDataRaw(t, resourceGithubRepository().Schema, map[string]any{
2073+
"name": "test-repo",
2074+
})
2075+
d.SetId("test-repo")
2076+
2077+
repo := &github.Repository{
2078+
Name: github.String("test-repo"),
2079+
FullName: github.String("test-org/test-repo"),
2080+
Owner: nil,
2081+
}
2082+
2083+
if repo.Owner != nil {
2084+
_ = d.Set("owner", repo.Owner.GetLogin())
2085+
}
2086+
2087+
owner, ok := d.GetOk("owner")
2088+
if ok && owner != "" {
2089+
t.Errorf("Expected owner to be empty when repo.Owner is nil, got: %v", owner)
2090+
}
2091+
2092+
repo.Owner = &github.User{
2093+
Login: github.String("test-owner"),
2094+
}
2095+
2096+
if repo.Owner != nil {
2097+
_ = d.Set("owner", repo.Owner.GetLogin())
2098+
}
2099+
2100+
owner, ok = d.GetOk("owner")
2101+
if !ok {
2102+
t.Error("Expected owner to be set when repo.Owner is not nil")
2103+
}
2104+
if owner != "test-owner" {
2105+
t.Errorf("Expected owner to be 'test-owner', got: %v", owner)
2106+
}
2107+
}
2108+
20282109
func createForkedRepository(repositoryName string) error {
20292110
config := Config{BaseURL: "https://api.github.com/", Owner: testOrganizationFunc(), Token: testToken}
20302111
meta, err := config.Meta()

website/docs/d/repository.html.markdown

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ The following arguments are supported:
2525

2626
* `full_name` - (Optional) Full name of the repository (in `org/name` format).
2727

28+
* `owner` - (Optional) Owner of the repository. If not provided, the owner specified in the provider configuration will be used.
29+
2830
## Attributes Reference
2931

3032
* `node_id` - the Node ID of the repository.

0 commit comments

Comments
 (0)