Skip to content

Commit ab3ebdf

Browse files
Add Repository Group resource with IAM (#12424) (#8824)
[upstream:a323c5a26dcc8ec0a4a91d01e7f5670e8f92dacd] Signed-off-by: Modular Magician <[email protected]>
1 parent cbaba75 commit ab3ebdf

15 files changed

+2333
-39
lines changed

.changelog/12424.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
```release-note:new-resource
2+
`google_gemini_repository_group` (beta only)
3+
```
4+
```release-note:new-resource
5+
`google_gemini_repository_group_iam_member` (beta only)
6+
```
7+
```release-note:new-resource
8+
`google_gemini_repository_group_iam_binding` (beta only)
9+
```
10+
```release-note:new-resource
11+
`google_gemini_repository_group_iam_policy` (beta only)
12+
```

google-beta/acctest/bootstrap_test_utils.go

Lines changed: 313 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"strings"
1212
"testing"
1313
"time"
14+
// For beta tests only
15+
"net/http"
1416

1517
"github.com/hashicorp/terraform-provider-google-beta/google-beta/envvar"
1618
tpgcompute "github.com/hashicorp/terraform-provider-google-beta/google-beta/services/compute"
@@ -506,7 +508,7 @@ func NewServiceNetworkSettings(options ...func(*ServiceNetworkSettings)) *Servic
506508
// That is the reason to use the shared service networking connection for test resources.
507509
// https://cloud.google.com/vpc/docs/configure-private-services-access#removing-connection
508510
//
509-
// testId specifies the test for which a shared network and a gobal address are used/initialized.
511+
// testId specifies the test for which a shared network and a global address are used/initialized.
510512
func BootstrapSharedServiceNetworkingConnection(t *testing.T, testId string, params ...func(*ServiceNetworkSettings)) string {
511513
settings := NewServiceNetworkSettings(params...)
512514
parentService := "services/" + settings.ParentService
@@ -1434,6 +1436,316 @@ func SetupProjectsAndGetAccessToken(org, billing, pid, service string, config *t
14341436
return accessToken, nil
14351437
}
14361438

1439+
// For bootstrapping Developer Connect git repository link
1440+
const SharedGitRepositoryLinkIdPrefix = "tf-bootstrap-git-repository-"
1441+
1442+
func BootstrapGitRepository(t *testing.T, gitRepositoryLinkId, location, cloneUri, parentConnectionId string) string {
1443+
gitRepositoryLinkId = SharedGitRepositoryLinkIdPrefix + gitRepositoryLinkId
1444+
1445+
config := BootstrapConfig(t)
1446+
if config == nil {
1447+
t.Fatal("Could not bootstrap config.")
1448+
}
1449+
1450+
log.Printf("[DEBUG] Getting shared git repository link %q", gitRepositoryLinkId)
1451+
1452+
getURL := fmt.Sprintf("%sprojects/%s/locations/%s/connections/%s/gitRepositoryLinks/%s",
1453+
config.DeveloperConnectBasePath, config.Project, location, parentConnectionId, gitRepositoryLinkId)
1454+
1455+
headers := make(http.Header)
1456+
_, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
1457+
Config: config,
1458+
Method: "GET",
1459+
Project: config.Project,
1460+
RawURL: getURL,
1461+
UserAgent: config.UserAgent,
1462+
Headers: headers,
1463+
})
1464+
1465+
if err != nil && transport_tpg.IsGoogleApiErrorWithCode(err, 404) {
1466+
log.Printf("[DEBUG] Git repository link %q not found, bootstrapping", gitRepositoryLinkId)
1467+
obj := map[string]interface{}{
1468+
"clone_uri": cloneUri,
1469+
"annotations": map[string]string{},
1470+
}
1471+
1472+
postURL := fmt.Sprintf("%sprojects/%s/locations/%s/connections/%s/gitRepositoryLinks?gitRepositoryLinkId=%s",
1473+
config.DeveloperConnectBasePath, config.Project, location, parentConnectionId, gitRepositoryLinkId)
1474+
headers := make(http.Header)
1475+
_, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
1476+
Config: config,
1477+
Method: "POST",
1478+
Project: config.Project,
1479+
RawURL: postURL,
1480+
UserAgent: config.UserAgent,
1481+
Body: obj,
1482+
Timeout: 20 * time.Minute,
1483+
Headers: headers,
1484+
})
1485+
if err != nil {
1486+
t.Fatalf("Error bootstrapping git repository link %q: %s", gitRepositoryLinkId, err)
1487+
}
1488+
1489+
_, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
1490+
Config: config,
1491+
Method: "GET",
1492+
Project: config.Project,
1493+
RawURL: getURL,
1494+
UserAgent: config.UserAgent,
1495+
Timeout: 20 * time.Minute,
1496+
Headers: headers,
1497+
})
1498+
if err != nil {
1499+
t.Fatalf("Error getting git repository link %q: %s", gitRepositoryLinkId, err)
1500+
}
1501+
}
1502+
1503+
return gitRepositoryLinkId
1504+
}
1505+
1506+
const SharedConnectionIdPrefix = "tf-bootstrap-developer-connect-connection-"
1507+
1508+
// For bootstrapping Developer Connect connection resources
1509+
func BootstrapDeveloperConnection(t *testing.T, connectionId, location, tokenResource string, appInstallationId int) string {
1510+
connectionId = SharedConnectionIdPrefix + connectionId
1511+
1512+
config := BootstrapConfig(t)
1513+
if config == nil {
1514+
t.Fatal("Could not bootstrap config.")
1515+
}
1516+
1517+
log.Printf("[DEBUG] Getting shared developer connection %q", connectionId)
1518+
1519+
getURL := fmt.Sprintf("%sprojects/%s/locations/%s/connections/%s",
1520+
config.DeveloperConnectBasePath, config.Project, location, connectionId)
1521+
1522+
headers := make(http.Header)
1523+
_, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
1524+
Config: config,
1525+
Method: "GET",
1526+
Project: config.Project,
1527+
RawURL: getURL,
1528+
UserAgent: config.UserAgent,
1529+
Headers: headers,
1530+
})
1531+
1532+
if err != nil {
1533+
log.Printf("[DEBUG] Developer connection %q not found, bootstrapping", connectionId)
1534+
authorizerCredential := map[string]string{
1535+
"oauth_token_secret_version": tokenResource,
1536+
}
1537+
githubConfig := map[string]interface{}{
1538+
"github_app": "DEVELOPER_CONNECT",
1539+
"app_installation_id": appInstallationId,
1540+
"authorizer_credential": authorizerCredential,
1541+
}
1542+
obj := map[string]interface{}{
1543+
"disabled": false,
1544+
"github_config": githubConfig,
1545+
}
1546+
1547+
postURL := fmt.Sprintf("%sprojects/%s/locations/%s/connections?connectionId=%s",
1548+
config.DeveloperConnectBasePath, config.Project, location, connectionId)
1549+
headers := make(http.Header)
1550+
_, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
1551+
Config: config,
1552+
Method: "POST",
1553+
Project: config.Project,
1554+
RawURL: postURL,
1555+
UserAgent: config.UserAgent,
1556+
Body: obj,
1557+
Timeout: 20 * time.Minute,
1558+
Headers: headers,
1559+
})
1560+
if err != nil {
1561+
t.Fatalf("Error bootstrapping developer connection %q: %s", connectionId, err)
1562+
}
1563+
1564+
_, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
1565+
Config: config,
1566+
Method: "GET",
1567+
Project: config.Project,
1568+
RawURL: getURL,
1569+
UserAgent: config.UserAgent,
1570+
Timeout: 20 * time.Minute,
1571+
Headers: headers,
1572+
})
1573+
if err != nil {
1574+
t.Fatalf("Error getting developer connection %q: %s", connectionId, err)
1575+
}
1576+
}
1577+
1578+
return connectionId
1579+
}
1580+
1581+
const SharedRepositoryGroupPrefix = "tf-bootstrap-repo-group-"
1582+
1583+
func BoostrapSharedRepositoryGroup(t *testing.T, repositoryGroupId, location, labels, codeRepositoryIndexId, resource string) string {
1584+
repositoryGroupId = SharedRepositoryGroupPrefix + repositoryGroupId
1585+
1586+
config := BootstrapConfig(t)
1587+
if config == nil {
1588+
t.Fatal("Could not bootstrap config.")
1589+
}
1590+
1591+
log.Printf("[DEBUG] Getting shared repository group %q", repositoryGroupId)
1592+
1593+
getURL := fmt.Sprintf("%sprojects/%s/locations/%s/codeRepositoryIndexes/%s/repositoryGroups/%s",
1594+
config.GeminiBasePath, config.Project, location, codeRepositoryIndexId, repositoryGroupId)
1595+
1596+
headers := make(http.Header)
1597+
_, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
1598+
Config: config,
1599+
Method: "GET",
1600+
Project: config.Project,
1601+
RawURL: getURL,
1602+
UserAgent: config.UserAgent,
1603+
Headers: headers,
1604+
})
1605+
if err != nil {
1606+
log.Printf("[DEBUG] Repository group %q not found, bootstrapping", codeRepositoryIndexId)
1607+
repositories := [1]interface{}{map[string]string{
1608+
"resource": resource,
1609+
"branch_pattern": "main",
1610+
}}
1611+
postURL := fmt.Sprintf("%sprojects/%s/locations/%s/codeRepositoryIndexes/%s/repositoryGroups?repositoryGroupId=%s",
1612+
config.GeminiBasePath, config.Project, location, codeRepositoryIndexId, repositoryGroupId)
1613+
obj := map[string]interface{}{
1614+
"repositories": repositories,
1615+
}
1616+
if labels != "" {
1617+
obj["labels"] = labels
1618+
}
1619+
1620+
headers := make(http.Header)
1621+
for {
1622+
_, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
1623+
Config: config,
1624+
Method: "POST",
1625+
Project: config.Project,
1626+
RawURL: postURL,
1627+
UserAgent: config.UserAgent,
1628+
Body: obj,
1629+
Timeout: 20 * time.Minute,
1630+
Headers: headers,
1631+
})
1632+
if err != nil {
1633+
if transport_tpg.IsGoogleApiErrorWithCode(err, 409) {
1634+
errMsg := fmt.Sprintf("%s", err)
1635+
if strings.Contains(errMsg, "unable to queue the operation") {
1636+
log.Printf("[DEBUG] Waiting for enqueued operation to finish before creating RepositoryGroup: %#v", obj)
1637+
time.Sleep(10 * time.Second)
1638+
} else if strings.Contains(errMsg, "parent resource not in ready state") {
1639+
log.Printf("[DEBUG] Waiting for parent resource to become active before creating RepositoryGroup: %#v", obj)
1640+
time.Sleep(1 * time.Minute)
1641+
} else {
1642+
t.Fatalf("Error creating RepositoryGroup: %s", err)
1643+
}
1644+
} else {
1645+
t.Fatalf("Error creating repository group %q: %s", repositoryGroupId, err)
1646+
}
1647+
} else {
1648+
break
1649+
}
1650+
}
1651+
1652+
_, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
1653+
Config: config,
1654+
Method: "GET",
1655+
Project: config.Project,
1656+
RawURL: getURL,
1657+
UserAgent: config.UserAgent,
1658+
Timeout: 20 * time.Minute,
1659+
Headers: headers,
1660+
})
1661+
if err != nil {
1662+
t.Errorf("Error getting repository group %q: %s", repositoryGroupId, err)
1663+
}
1664+
}
1665+
1666+
return repositoryGroupId
1667+
}
1668+
1669+
// BootstrapSharedCodeRepositoryIndex will create a code repository index
1670+
// if it hasn't been created in the test project.
1671+
//
1672+
// BootstrapSharedCodeRepositoryIndex returns a persistent code repository index
1673+
// for a test or set of tests.
1674+
//
1675+
// Deletion of code repository index takes a few minutes, and creation of it
1676+
// currently takes about half an hour.
1677+
// That is the reason to use the shared code repository indexes for test resources.
1678+
const SharedCodeRepositoryIndexPrefix = "tf-bootstrap-cri-"
1679+
1680+
func BootstrapSharedCodeRepositoryIndex(t *testing.T, codeRepositoryIndexId, location, kmsKey string, labels map[string]string) string {
1681+
codeRepositoryIndexId = SharedCodeRepositoryIndexPrefix + codeRepositoryIndexId
1682+
1683+
config := BootstrapConfig(t)
1684+
if config == nil {
1685+
t.Fatal("Could not bootstrap config.")
1686+
}
1687+
1688+
log.Printf("[DEBUG] Getting shared code repository index %q", codeRepositoryIndexId)
1689+
1690+
getURL := fmt.Sprintf("%sprojects/%s/locations/%s/codeRepositoryIndexes/%s", config.GeminiBasePath, config.Project, location, codeRepositoryIndexId)
1691+
1692+
headers := make(http.Header)
1693+
_, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
1694+
Config: config,
1695+
Method: "GET",
1696+
Project: config.Project,
1697+
RawURL: getURL,
1698+
UserAgent: config.UserAgent,
1699+
Timeout: 90 * time.Minute,
1700+
Headers: headers,
1701+
})
1702+
1703+
// CRI not found responds with 404 not found
1704+
if err != nil && transport_tpg.IsGoogleApiErrorWithCode(err, 404) {
1705+
log.Printf("[DEBUG] Code repository index %q not found, bootstrapping", codeRepositoryIndexId)
1706+
postURL := fmt.Sprintf("%sprojects/%s/locations/%s/codeRepositoryIndexes?codeRepositoryIndexId=%s", config.GeminiBasePath, config.Project, location, codeRepositoryIndexId)
1707+
obj := make(map[string]interface{})
1708+
if labels != nil {
1709+
obj["labels"] = labels
1710+
}
1711+
if kmsKey != "" {
1712+
obj["kmsKey"] = kmsKey
1713+
}
1714+
1715+
headers := make(http.Header)
1716+
_, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
1717+
Config: config,
1718+
Method: "POST",
1719+
Project: config.Project,
1720+
RawURL: postURL,
1721+
UserAgent: config.UserAgent,
1722+
Body: obj,
1723+
Timeout: 90 * time.Minute,
1724+
Headers: headers,
1725+
})
1726+
if err != nil {
1727+
t.Fatalf("Error creating code repository index %q: %s", codeRepositoryIndexId, err)
1728+
}
1729+
1730+
_, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
1731+
Config: config,
1732+
Method: "GET",
1733+
Project: config.Project,
1734+
RawURL: getURL,
1735+
UserAgent: config.UserAgent,
1736+
Timeout: 90 * time.Minute,
1737+
Headers: headers,
1738+
})
1739+
if err != nil {
1740+
t.Fatalf("Error getting code repository index %q: %s", codeRepositoryIndexId, err)
1741+
}
1742+
} else if err != nil {
1743+
t.Fatalf("Error getting code repository index %q: %s", codeRepositoryIndexId, err)
1744+
}
1745+
1746+
return codeRepositoryIndexId
1747+
}
1748+
14371749
const sharedTagKeyPrefix = "tf-bootstrap-tagkey"
14381750

14391751
func BootstrapSharedTestTagKey(t *testing.T, testId string) string {

google-beta/provider/provider_mmv1_resources.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ var generatedIAMDatasources = map[string]*schema.Resource{
433433
"google_dataproc_metastore_federation_iam_policy": tpgiamresource.DataSourceIamPolicy(dataprocmetastore.DataprocMetastoreFederationIamSchema, dataprocmetastore.DataprocMetastoreFederationIamUpdaterProducer),
434434
"google_dataproc_metastore_service_iam_policy": tpgiamresource.DataSourceIamPolicy(dataprocmetastore.DataprocMetastoreServiceIamSchema, dataprocmetastore.DataprocMetastoreServiceIamUpdaterProducer),
435435
"google_dns_managed_zone_iam_policy": tpgiamresource.DataSourceIamPolicy(dns.DNSManagedZoneIamSchema, dns.DNSManagedZoneIamUpdaterProducer),
436+
"google_gemini_repository_group_iam_policy": tpgiamresource.DataSourceIamPolicy(gemini.GeminiRepositoryGroupIamSchema, gemini.GeminiRepositoryGroupIamUpdaterProducer),
436437
"google_gke_backup_backup_plan_iam_policy": tpgiamresource.DataSourceIamPolicy(gkebackup.GKEBackupBackupPlanIamSchema, gkebackup.GKEBackupBackupPlanIamUpdaterProducer),
437438
"google_gke_backup_restore_plan_iam_policy": tpgiamresource.DataSourceIamPolicy(gkebackup.GKEBackupRestorePlanIamSchema, gkebackup.GKEBackupRestorePlanIamUpdaterProducer),
438439
"google_gke_hub_membership_iam_policy": tpgiamresource.DataSourceIamPolicy(gkehub.GKEHubMembershipIamSchema, gkehub.GKEHubMembershipIamUpdaterProducer),
@@ -508,9 +509,9 @@ var handwrittenIAMDatasources = map[string]*schema.Resource{
508509
}
509510

510511
// Resources
511-
// Generated resources: 563
512-
// Generated IAM resources: 291
513-
// Total generated resources: 854
512+
// Generated resources: 564
513+
// Generated IAM resources: 294
514+
// Total generated resources: 858
514515
var generatedResources = map[string]*schema.Resource{
515516
"google_folder_access_approval_settings": accessapproval.ResourceAccessApprovalFolderSettings(),
516517
"google_organization_access_approval_settings": accessapproval.ResourceAccessApprovalOrganizationSettings(),
@@ -978,6 +979,10 @@ var generatedResources = map[string]*schema.Resource{
978979
"google_firestore_field": firestore.ResourceFirestoreField(),
979980
"google_firestore_index": firestore.ResourceFirestoreIndex(),
980981
"google_gemini_code_repository_index": gemini.ResourceGeminiCodeRepositoryIndex(),
982+
"google_gemini_repository_group": gemini.ResourceGeminiRepositoryGroup(),
983+
"google_gemini_repository_group_iam_binding": tpgiamresource.ResourceIamBinding(gemini.GeminiRepositoryGroupIamSchema, gemini.GeminiRepositoryGroupIamUpdaterProducer, gemini.GeminiRepositoryGroupIdParseFunc),
984+
"google_gemini_repository_group_iam_member": tpgiamresource.ResourceIamMember(gemini.GeminiRepositoryGroupIamSchema, gemini.GeminiRepositoryGroupIamUpdaterProducer, gemini.GeminiRepositoryGroupIdParseFunc),
985+
"google_gemini_repository_group_iam_policy": tpgiamresource.ResourceIamPolicy(gemini.GeminiRepositoryGroupIamSchema, gemini.GeminiRepositoryGroupIamUpdaterProducer, gemini.GeminiRepositoryGroupIdParseFunc),
981986
"google_gke_backup_backup_plan": gkebackup.ResourceGKEBackupBackupPlan(),
982987
"google_gke_backup_backup_plan_iam_binding": tpgiamresource.ResourceIamBinding(gkebackup.GKEBackupBackupPlanIamSchema, gkebackup.GKEBackupBackupPlanIamUpdaterProducer, gkebackup.GKEBackupBackupPlanIdParseFunc),
983988
"google_gke_backup_backup_plan_iam_member": tpgiamresource.ResourceIamMember(gkebackup.GKEBackupBackupPlanIamSchema, gkebackup.GKEBackupBackupPlanIamUpdaterProducer, gkebackup.GKEBackupBackupPlanIdParseFunc),

google-beta/services/gemini/gemini_operation.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,12 @@ func (w *GeminiOperationWaiter) QueryOp() (interface{}, error) {
4242
url := fmt.Sprintf("%s%s", w.Config.GeminiBasePath, w.CommonOperationWaiter.Op.Name)
4343

4444
return transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
45-
Config: w.Config,
46-
Method: "GET",
47-
Project: w.Project,
48-
RawURL: url,
49-
UserAgent: w.UserAgent,
45+
Config: w.Config,
46+
Method: "GET",
47+
Project: w.Project,
48+
RawURL: url,
49+
UserAgent: w.UserAgent,
50+
ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{transport_tpg.IsCodeRepositoryIndexUnreadyError, transport_tpg.IsRepositoryGroupQueueError},
5051
})
5152
}
5253

0 commit comments

Comments
 (0)