Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions examples/cost_centers/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
terraform {
required_providers {
github = {
source = "integrations/github"
version = "~> 6.0"
}
}
}

provider "github" {
token = var.github_token
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The enterprise should have at least the correct structure of configs

Suggested change
token = var.github_token
token = var.github_token
owner = "my-enterprise"

}

variable "github_token" {
description = "GitHub classic personal access token (PAT) for an enterprise admin"
type = string
sensitive = true
}

variable "enterprise_slug" {
description = "The GitHub Enterprise slug"
type = string
}

variable "cost_center_name" {
description = "Name for the cost center"
type = string
}

variable "users" {
description = "Usernames to assign to the cost center"
type = list(string)
default = []
}

variable "organizations" {
description = "Organization logins to assign to the cost center"
type = list(string)
default = []
}

variable "repositories" {
description = "Repositories (full name, e.g. org/repo) to assign to the cost center"
type = list(string)
default = []
}

resource "github_enterprise_cost_center" "example" {
enterprise_slug = var.enterprise_slug
name = var.cost_center_name

# Authoritative assignments: Terraform will add/remove to match these lists.
users = var.users
organizations = var.organizations
repositories = var.repositories
}

data "github_enterprise_cost_center" "by_id" {
enterprise_slug = var.enterprise_slug
cost_center_id = github_enterprise_cost_center.example.id
}

data "github_enterprise_cost_centers" "active" {
enterprise_slug = var.enterprise_slug
state = "active"

depends_on = [github_enterprise_cost_center.example]
}

output "cost_center" {
description = "Created cost center"
value = {
id = github_enterprise_cost_center.example.id
name = github_enterprise_cost_center.example.name
state = github_enterprise_cost_center.example.state
azure_subscription = github_enterprise_cost_center.example.azure_subscription
}
}

output "cost_center_resources" {
description = "Effective assignments (read from API)"
value = {
users = sort(tolist(github_enterprise_cost_center.example.users))
organizations = sort(tolist(github_enterprise_cost_center.example.organizations))
repositories = sort(tolist(github_enterprise_cost_center.example.repositories))
}
}

output "cost_center_from_data_source" {
description = "Cost center fetched by data source"
value = {
id = data.github_enterprise_cost_center.by_id.cost_center_id
name = data.github_enterprise_cost_center.by_id.name
state = data.github_enterprise_cost_center.by_id.state
users = sort(tolist(data.github_enterprise_cost_center.by_id.users))
organizations = sort(tolist(data.github_enterprise_cost_center.by_id.organizations))
repositories = sort(tolist(data.github_enterprise_cost_center.by_id.repositories))
}
}
124 changes: 124 additions & 0 deletions github/data_source_github_enterprise_cost_center.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package github

import (
"context"
"fmt"
"sort"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func dataSourceGithubEnterpriseCostCenter() *schema.Resource {
return &schema.Resource{
ReadContext: dataSourceGithubEnterpriseCostCenterRead,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a top-level Description attribute

Schema: map[string]*schema.Schema{
"enterprise_slug": {
Type: schema.TypeString,
Required: true,
Description: "The slug of the enterprise.",
},
"cost_center_id": {
Type: schema.TypeString,
Required: true,
Description: "The ID of the cost center.",
},
"name": {
Type: schema.TypeString,
Computed: true,
Description: "The name of the cost center.",
},
"state": {
Type: schema.TypeString,
Computed: true,
Description: "The state of the cost center.",
},
"azure_subscription": {
Type: schema.TypeString,
Computed: true,
Description: "The Azure subscription associated with the cost center.",
},
"users": {
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Description: "The usernames assigned to this cost center.",
},
"organizations": {
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Description: "The organization logins assigned to this cost center.",
},
"repositories": {
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Description: "The repositories (full name) assigned to this cost center.",
},
"resources": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Type: schema.TypeString,
Computed: true,
},
"name": {
Type: schema.TypeString,
Computed: true,
},
},
},
},
},
}
}

func dataSourceGithubEnterpriseCostCenterRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
client := meta.(*Owner).v3client
enterpriseSlug := d.Get("enterprise_slug").(string)
costCenterID := d.Get("cost_center_id").(string)

ctx = context.WithValue(ctx, ctxId, fmt.Sprintf("%s/%s", enterpriseSlug, costCenterID))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're removing this pattern as it's not being used

Suggested change
ctx = context.WithValue(ctx, ctxId, fmt.Sprintf("%s/%s", enterpriseSlug, costCenterID))


cc, _, err := client.Enterprise.GetCostCenter(ctx, enterpriseSlug, costCenterID)
if err != nil {
return diag.FromErr(err)
}

d.SetId(costCenterID)
_ = d.Set("name", cc.Name)

state := strings.ToLower(cc.GetState())
if state == "" {
state = "active"
}
_ = d.Set("state", state)
_ = d.Set("azure_subscription", cc.GetAzureSubscription())

resources := make([]map[string]any, 0)
for _, r := range cc.Resources {
if r == nil {
continue
}
resources = append(resources, map[string]any{
"type": r.Type,
"name": r.Name,
})
}
_ = d.Set("resources", resources)

users, organizations, repositories := costCenterSplitResources(cc.Resources)
sort.Strings(users)
sort.Strings(organizations)
sort.Strings(repositories)
_ = d.Set("users", stringSliceToAnySlice(users))
_ = d.Set("organizations", stringSliceToAnySlice(organizations))
_ = d.Set("repositories", stringSliceToAnySlice(repositories))
Comment on lines +113 to +121
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that you chose to group these, that makes this more useful. But I wonder if it would make sense to omit the resources attribute or only leave things in there which are not of the 3 types?


return nil
}
73 changes: 73 additions & 0 deletions github/data_source_github_enterprise_cost_center_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package github

import (
"fmt"
"os"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccGithubEnterpriseCostCenterDataSource(t *testing.T) {
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)

testEnterpriseCostCenterOrganization := os.Getenv("ENTERPRISE_TEST_ORGANIZATION")
testEnterpriseCostCenterRepository := os.Getenv("ENTERPRISE_TEST_REPOSITORY")
testEnterpriseCostCenterUsers := os.Getenv("ENTERPRISE_TEST_USERS")

if testEnterpriseCostCenterOrganization == "" {
t.Skip("Skipping because `ENTERPRISE_TEST_ORGANIZATION` is not set")
}
if testEnterpriseCostCenterRepository == "" {
t.Skip("Skipping because `ENTERPRISE_TEST_REPOSITORY` is not set")
}
if testEnterpriseCostCenterUsers == "" {
t.Skip("Skipping because `ENTERPRISE_TEST_USERS` is not set")
}
Comment on lines +15 to +27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please utilize the existing structures in acc_test.go for these. And if something is missing add it there


users := splitCommaSeparated(testEnterpriseCostCenterUsers)
if len(users) == 0 {
t.Skip("Skipping because `ENTERPRISE_TEST_USERS` must contain at least one username")
}

userList := fmt.Sprintf("%q", users[0])

config := fmt.Sprintf(`
data "github_enterprise" "enterprise" {
slug = "%s"
}

resource "github_enterprise_cost_center" "test" {
enterprise_slug = data.github_enterprise.enterprise.slug
name = "tf-acc-test-%s"

users = [%s]
organizations = [%q]
repositories = [%q]
}

data "github_enterprise_cost_center" "test" {
enterprise_slug = data.github_enterprise.enterprise.slug
cost_center_id = github_enterprise_cost_center.test.id
}
`, testAccConf.enterpriseSlug, randomID, userList, testEnterpriseCostCenterOrganization, testEnterpriseCostCenterRepository)

check := resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair("data.github_enterprise_cost_center.test", "cost_center_id", "github_enterprise_cost_center.test", "id"),
resource.TestCheckResourceAttrPair("data.github_enterprise_cost_center.test", "name", "github_enterprise_cost_center.test", "name"),
resource.TestCheckResourceAttr("data.github_enterprise_cost_center.test", "state", "active"),
resource.TestCheckResourceAttr("data.github_enterprise_cost_center.test", "organizations.#", "1"),
resource.TestCheckTypeSetElemAttr("data.github_enterprise_cost_center.test", "organizations.*", testEnterpriseCostCenterOrganization),
resource.TestCheckResourceAttr("data.github_enterprise_cost_center.test", "repositories.#", "1"),
resource.TestCheckTypeSetElemAttr("data.github_enterprise_cost_center.test", "repositories.*", testEnterpriseCostCenterRepository),
resource.TestCheckResourceAttr("data.github_enterprise_cost_center.test", "users.#", "1"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Since the count of users depends on the input list length, this check should be using the len of the usersList

resource.TestCheckTypeSetElemAttr("data.github_enterprise_cost_center.test", "users.*", users[0]),
)
Comment on lines +56 to +66
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please inline this variable


resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessMode(t, enterprise) },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
PreCheck: func() { skipUnlessMode(t, enterprise) },
PreCheck: func() { skipUnlessEnterprise(t) },

Providers: testAccProviders,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Providers: testAccProviders,
ProviderFactories: providerFactories,

Steps: []resource.TestStep{{Config: config, Check: check}},
})
}
Loading