Skip to content

Commit 0efc21d

Browse files
Add data source for retrieving the ancestors for a project (#13029) (#9326)
[upstream:2ca17b346bf45ca03239fcbac958c8971e6e6244] Signed-off-by: Modular Magician <[email protected]>
1 parent 60a315c commit 0efc21d

File tree

5 files changed

+340
-0
lines changed

5 files changed

+340
-0
lines changed

.changelog/13029.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:new-datasource
2+
`google_project_ancestry`
3+
```

google-beta/provider/provider_mmv1_resources.go

+1
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ var handwrittenDatasources = map[string]*schema.Resource{
337337
"google_privileged_access_manager_entitlement": privilegedaccessmanager.DataSourceGooglePrivilegedAccessManagerEntitlement(),
338338
"google_project": resourcemanager.DataSourceGoogleProject(),
339339
"google_projects": resourcemanager.DataSourceGoogleProjects(),
340+
"google_project_ancestry": resourcemanager.DataSourceGoogleProjectAncestry(),
340341
"google_project_organization_policy": resourcemanager.DataSourceGoogleProjectOrganizationPolicy(),
341342
"google_project_service": resourcemanager.DataSourceGoogleProjectService(),
342343
"google_pubsub_subscription": pubsub.DataSourceGooglePubsubSubscription(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
// Copyright (c) HashiCorp, Inc.
4+
// SPDX-License-Identifier: MPL-2.0
5+
package resourcemanager
6+
7+
import (
8+
"context"
9+
"fmt"
10+
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
12+
"github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource"
13+
transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport"
14+
"google.golang.org/api/cloudresourcemanager/v1"
15+
)
16+
17+
func DataSourceGoogleProjectAncestry() *schema.Resource {
18+
return &schema.Resource{
19+
Read: datasourceGoogleProjectAncestryRead,
20+
Schema: map[string]*schema.Schema{
21+
"ancestors": {
22+
Type: schema.TypeList,
23+
Computed: true,
24+
Elem: &schema.Resource{
25+
Schema: map[string]*schema.Schema{
26+
"id": {
27+
Type: schema.TypeString,
28+
Computed: true,
29+
},
30+
"type": {
31+
Type: schema.TypeString,
32+
Computed: true,
33+
},
34+
},
35+
},
36+
},
37+
"org_id": {
38+
Type: schema.TypeString,
39+
Computed: true,
40+
},
41+
"parent_id": {
42+
Type: schema.TypeString,
43+
Computed: true,
44+
},
45+
"parent_type": {
46+
Type: schema.TypeString,
47+
Computed: true,
48+
},
49+
"project": {
50+
Type: schema.TypeString,
51+
Optional: true,
52+
},
53+
},
54+
}
55+
}
56+
57+
func datasourceGoogleProjectAncestryRead(d *schema.ResourceData, meta interface{}) error {
58+
config := meta.(*transport_tpg.Config)
59+
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
60+
if err != nil {
61+
return err
62+
}
63+
64+
project, err := tpgresource.GetProject(d, config)
65+
if err != nil {
66+
return fmt.Errorf("Error fetching project for ancestry: %s", err)
67+
}
68+
69+
request := &cloudresourcemanager.GetAncestryRequest{}
70+
response, err := config.NewResourceManagerClient(userAgent).Projects.GetAncestry(project, request).Context(context.Background()).Do()
71+
if err != nil {
72+
return fmt.Errorf("Error retrieving project ancestry: %s", err)
73+
}
74+
75+
ancestors := make([]map[string]interface{}, 0)
76+
var orgID string
77+
var parentID string
78+
var parentType string
79+
80+
for _, a := range response.Ancestor {
81+
if a.ResourceId == nil {
82+
continue
83+
}
84+
85+
ancestorData := map[string]interface{}{
86+
"id": a.ResourceId.Id,
87+
"type": a.ResourceId.Type,
88+
}
89+
90+
ancestors = append(ancestors, ancestorData)
91+
92+
if a.ResourceId.Type == "organization" {
93+
orgID = a.ResourceId.Id
94+
}
95+
}
96+
97+
if err := d.Set("ancestors", ancestors); err != nil {
98+
return fmt.Errorf("Error setting ancestors: %s", err)
99+
}
100+
101+
if err := d.Set("org_id", orgID); err != nil {
102+
return fmt.Errorf("Error setting org_id: %s", err)
103+
}
104+
105+
if err := d.Set("project", project); err != nil {
106+
return fmt.Errorf("Error setting project: %s", err)
107+
}
108+
109+
if len(ancestors) > 1 {
110+
parent := ancestors[1]
111+
if id, ok := parent["id"].(string); ok {
112+
parentID = id
113+
}
114+
if pType, ok := parent["type"].(string); ok {
115+
parentType = pType
116+
}
117+
118+
if err := d.Set("parent_id", parentID); err != nil {
119+
return fmt.Errorf("Error setting parent_id: %s", err)
120+
}
121+
122+
if err := d.Set("parent_type", parentType); err != nil {
123+
return fmt.Errorf("Error setting parent_type: %s", err)
124+
}
125+
}
126+
127+
d.SetId(fmt.Sprintf("projects/%s", project))
128+
129+
return nil
130+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
package resourcemanager_test
4+
5+
import (
6+
"fmt"
7+
"testing"
8+
9+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
10+
"github.com/hashicorp/terraform-provider-google-beta/google-beta/acctest"
11+
"github.com/hashicorp/terraform-provider-google-beta/google-beta/envvar"
12+
)
13+
14+
func TestAccDataSourceGoogleProjectAncestry_basic(t *testing.T) {
15+
t.Parallel()
16+
17+
// Common resource configuration
18+
staticPrefix := "tf-test-"
19+
randomSuffix := "-" + acctest.RandString(t, 10)
20+
orgID := envvar.GetTestOrgFromEnv(t)
21+
22+
// Configuration of resources
23+
folderThanos := staticPrefix + "thanos" + randomSuffix
24+
folderLoki := staticPrefix + "loki" + randomSuffix
25+
folderUltron := staticPrefix + "ultron" + randomSuffix
26+
projectThor := staticPrefix + "thor" + randomSuffix
27+
projectIronMan := staticPrefix + "ironman" + randomSuffix
28+
projectCap := staticPrefix + "cap" + randomSuffix
29+
projectHulk := staticPrefix + "hulk" + randomSuffix
30+
31+
// Configuration map used in test deployment
32+
context := map[string]interface{}{
33+
"org_id": orgID,
34+
"folder_thanos": folderThanos,
35+
"folder_loki": folderLoki,
36+
"folder_ultron": folderUltron,
37+
"project_thor": projectThor,
38+
"project_ironman": projectIronMan,
39+
"project_cap": projectCap,
40+
"project_hulk": projectHulk,
41+
}
42+
43+
acctest.VcrTest(t, resource.TestCase{
44+
PreCheck: func() { acctest.AccTestPreCheck(t) },
45+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
46+
Steps: []resource.TestStep{
47+
{
48+
Config: testAccCheckGoogleProjectAncestryConfig(context),
49+
Check: resource.ComposeTestCheckFunc(
50+
// Project thor under organization
51+
resource.TestCheckResourceAttr("data.google_project_ancestry.thor", "ancestors.#", "2"),
52+
resource.TestCheckResourceAttr("data.google_project_ancestry.thor", "ancestors.0.type", "project"),
53+
resource.TestCheckResourceAttr("data.google_project_ancestry.thor", "ancestors.1.type", "organization"),
54+
55+
// Project ironman under organization and thanos
56+
resource.TestCheckResourceAttr("data.google_project_ancestry.ironman", "ancestors.#", "3"),
57+
resource.TestCheckResourceAttr("data.google_project_ancestry.ironman", "ancestors.0.type", "project"),
58+
resource.TestCheckResourceAttr("data.google_project_ancestry.ironman", "ancestors.1.type", "folder"),
59+
resource.TestCheckResourceAttr("data.google_project_ancestry.ironman", "ancestors.2.type", "organization"),
60+
61+
// Project cap under organization, thanos and loki
62+
resource.TestCheckResourceAttr("data.google_project_ancestry.cap", "ancestors.#", "4"),
63+
resource.TestCheckResourceAttr("data.google_project_ancestry.cap", "ancestors.0.type", "project"),
64+
resource.TestCheckResourceAttr("data.google_project_ancestry.cap", "ancestors.1.type", "folder"),
65+
resource.TestCheckResourceAttr("data.google_project_ancestry.cap", "ancestors.2.type", "folder"),
66+
resource.TestCheckResourceAttr("data.google_project_ancestry.cap", "ancestors.3.type", "organization"),
67+
68+
// Project hulk under organization, thanos, loki and ultron
69+
resource.TestCheckResourceAttr("data.google_project_ancestry.hulk", "ancestors.#", "5"),
70+
resource.TestCheckResourceAttr("data.google_project_ancestry.hulk", "ancestors.0.type", "project"),
71+
resource.TestCheckResourceAttr("data.google_project_ancestry.hulk", "ancestors.1.type", "folder"),
72+
resource.TestCheckResourceAttr("data.google_project_ancestry.hulk", "ancestors.2.type", "folder"),
73+
resource.TestCheckResourceAttr("data.google_project_ancestry.hulk", "ancestors.3.type", "folder"),
74+
resource.TestCheckResourceAttr("data.google_project_ancestry.hulk", "ancestors.4.type", "organization"),
75+
),
76+
},
77+
},
78+
})
79+
}
80+
81+
func testAccCheckGoogleProjectAncestryConfig(context map[string]interface{}) string {
82+
return fmt.Sprintf(`
83+
locals {
84+
org_id = "%s"
85+
folder_thanos = "%s"
86+
folder_loki = "%s"
87+
folder_ultron = "%s"
88+
project_thor = "%s"
89+
project_ironman = "%s"
90+
project_cap = "%s"
91+
project_hulk = "%s"
92+
}
93+
94+
resource "google_folder" "thanos" {
95+
deletion_protection = false
96+
display_name = local.folder_thanos
97+
parent = "organizations/${local.org_id}"
98+
}
99+
100+
resource "google_folder" "loki" {
101+
deletion_protection = false
102+
display_name = local.folder_loki
103+
parent = google_folder.thanos.name
104+
}
105+
106+
resource "google_folder" "ultron" {
107+
deletion_protection = false
108+
display_name = local.folder_ultron
109+
parent = google_folder.loki.name
110+
}
111+
112+
resource "google_project" "thor" {
113+
deletion_policy = "DELETE"
114+
name = local.project_thor
115+
org_id = local.org_id
116+
project_id = local.project_thor
117+
}
118+
119+
resource "google_project" "ironman" {
120+
deletion_policy = "DELETE"
121+
folder_id = google_folder.thanos.id
122+
name = local.project_ironman
123+
project_id = local.project_ironman
124+
}
125+
126+
resource "google_project" "cap" {
127+
deletion_policy = "DELETE"
128+
folder_id = google_folder.loki.id
129+
name = local.project_cap
130+
project_id = local.project_cap
131+
}
132+
133+
resource "google_project" "hulk" {
134+
deletion_policy = "DELETE"
135+
folder_id = google_folder.ultron.id
136+
name = local.project_hulk
137+
project_id = local.project_hulk
138+
}
139+
140+
data "google_project_ancestry" "thor" {
141+
project = google_project.thor.project_id
142+
}
143+
144+
data "google_project_ancestry" "ironman" {
145+
project = google_project.ironman.project_id
146+
}
147+
148+
data "google_project_ancestry" "cap" {
149+
project = google_project.cap.project_id
150+
}
151+
152+
data "google_project_ancestry" "hulk" {
153+
project = google_project.hulk.project_id
154+
}
155+
`,
156+
context["org_id"].(string),
157+
context["folder_thanos"].(string),
158+
context["folder_loki"].(string),
159+
context["folder_ultron"].(string),
160+
context["project_thor"].(string),
161+
context["project_ironman"].(string),
162+
context["project_cap"].(string),
163+
context["project_hulk"].(string),
164+
)
165+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
subcategory: "Cloud Platform"
3+
description: |-
4+
Retrieve the ancestors for a project.
5+
---
6+
7+
# google_project_ancestry
8+
9+
Retrieve the ancestors for a project.
10+
See the [REST API](https://cloud.google.com/resource-manager/reference/rest/v1/projects/getAncestry) for more details.
11+
12+
## Example Usage
13+
14+
```hcl
15+
data "google_project_ancestry" "example" {
16+
project_id = "example-project"
17+
}
18+
```
19+
20+
## Argument Reference
21+
22+
The following arguments are supported:
23+
24+
* `project` - (Optional) The ID of the project. If it is not provided, the provider project is used.
25+
26+
## Attributes Reference
27+
28+
The following attributes are exported:
29+
30+
* `ancestors` - A list of the project's ancestors. Structure is [defined below](#nested_ancestors).
31+
32+
<a name="nested_ancestors"></a>The `ancestors` block supports:
33+
34+
* `id` - If it's a project, the `project_id` is exported, else the numeric folder id or organization id.
35+
* `type` - One of `"project"`, `"folder"` or `"organization"`.
36+
37+
---
38+
39+
* `org_id` - The optional user-assigned display name of the project.
40+
* `parent_id` - The parent's id.
41+
* `parent_type` - One of `"folder"` or `"organization"`.

0 commit comments

Comments
 (0)