-
Notifications
You must be signed in to change notification settings - Fork 915
[FEAT] Add support for Enterprise Cost Centers #3000
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
03186dd
549a6bc
e12d00f
eebfe10
e8cd57e
ee6b027
817b394
494122b
581e2be
7d29875
12d3f50
1d13386
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
| } | ||
|
|
||
| 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)) | ||
| } | ||
| } | ||
| 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, | ||||
|
|
||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a top-level |
||||
| 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)) | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||||
|
|
||||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||||
|
|
||||
| return nil | ||||
| } | ||||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please utilize the existing structures in |
||||||
|
|
||||||
| 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"), | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) }, | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| Providers: testAccProviders, | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| Steps: []resource.TestStep{{Config: config, Check: check}}, | ||||||
| }) | ||||||
| } | ||||||
There was a problem hiding this comment.
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