Skip to content

Commit

Permalink
Add Repository Group resource with IAM (#12424) (#8824)
Browse files Browse the repository at this point in the history
[upstream:a323c5a26dcc8ec0a4a91d01e7f5670e8f92dacd]

Signed-off-by: Modular Magician <[email protected]>
  • Loading branch information
modular-magician authored Dec 3, 2024
1 parent cbaba75 commit ab3ebdf
Show file tree
Hide file tree
Showing 15 changed files with 2,333 additions and 39 deletions.
12 changes: 12 additions & 0 deletions .changelog/12424.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
```release-note:new-resource
`google_gemini_repository_group` (beta only)
```
```release-note:new-resource
`google_gemini_repository_group_iam_member` (beta only)
```
```release-note:new-resource
`google_gemini_repository_group_iam_binding` (beta only)
```
```release-note:new-resource
`google_gemini_repository_group_iam_policy` (beta only)
```
314 changes: 313 additions & 1 deletion google-beta/acctest/bootstrap_test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"strings"
"testing"
"time"
// For beta tests only
"net/http"

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

// For bootstrapping Developer Connect git repository link
const SharedGitRepositoryLinkIdPrefix = "tf-bootstrap-git-repository-"

func BootstrapGitRepository(t *testing.T, gitRepositoryLinkId, location, cloneUri, parentConnectionId string) string {
gitRepositoryLinkId = SharedGitRepositoryLinkIdPrefix + gitRepositoryLinkId

config := BootstrapConfig(t)
if config == nil {
t.Fatal("Could not bootstrap config.")
}

log.Printf("[DEBUG] Getting shared git repository link %q", gitRepositoryLinkId)

getURL := fmt.Sprintf("%sprojects/%s/locations/%s/connections/%s/gitRepositoryLinks/%s",
config.DeveloperConnectBasePath, config.Project, location, parentConnectionId, gitRepositoryLinkId)

headers := make(http.Header)
_, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
Project: config.Project,
RawURL: getURL,
UserAgent: config.UserAgent,
Headers: headers,
})

if err != nil && transport_tpg.IsGoogleApiErrorWithCode(err, 404) {
log.Printf("[DEBUG] Git repository link %q not found, bootstrapping", gitRepositoryLinkId)
obj := map[string]interface{}{
"clone_uri": cloneUri,
"annotations": map[string]string{},
}

postURL := fmt.Sprintf("%sprojects/%s/locations/%s/connections/%s/gitRepositoryLinks?gitRepositoryLinkId=%s",
config.DeveloperConnectBasePath, config.Project, location, parentConnectionId, gitRepositoryLinkId)
headers := make(http.Header)
_, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "POST",
Project: config.Project,
RawURL: postURL,
UserAgent: config.UserAgent,
Body: obj,
Timeout: 20 * time.Minute,
Headers: headers,
})
if err != nil {
t.Fatalf("Error bootstrapping git repository link %q: %s", gitRepositoryLinkId, err)
}

_, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
Project: config.Project,
RawURL: getURL,
UserAgent: config.UserAgent,
Timeout: 20 * time.Minute,
Headers: headers,
})
if err != nil {
t.Fatalf("Error getting git repository link %q: %s", gitRepositoryLinkId, err)
}
}

return gitRepositoryLinkId
}

const SharedConnectionIdPrefix = "tf-bootstrap-developer-connect-connection-"

// For bootstrapping Developer Connect connection resources
func BootstrapDeveloperConnection(t *testing.T, connectionId, location, tokenResource string, appInstallationId int) string {
connectionId = SharedConnectionIdPrefix + connectionId

config := BootstrapConfig(t)
if config == nil {
t.Fatal("Could not bootstrap config.")
}

log.Printf("[DEBUG] Getting shared developer connection %q", connectionId)

getURL := fmt.Sprintf("%sprojects/%s/locations/%s/connections/%s",
config.DeveloperConnectBasePath, config.Project, location, connectionId)

headers := make(http.Header)
_, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
Project: config.Project,
RawURL: getURL,
UserAgent: config.UserAgent,
Headers: headers,
})

if err != nil {
log.Printf("[DEBUG] Developer connection %q not found, bootstrapping", connectionId)
authorizerCredential := map[string]string{
"oauth_token_secret_version": tokenResource,
}
githubConfig := map[string]interface{}{
"github_app": "DEVELOPER_CONNECT",
"app_installation_id": appInstallationId,
"authorizer_credential": authorizerCredential,
}
obj := map[string]interface{}{
"disabled": false,
"github_config": githubConfig,
}

postURL := fmt.Sprintf("%sprojects/%s/locations/%s/connections?connectionId=%s",
config.DeveloperConnectBasePath, config.Project, location, connectionId)
headers := make(http.Header)
_, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "POST",
Project: config.Project,
RawURL: postURL,
UserAgent: config.UserAgent,
Body: obj,
Timeout: 20 * time.Minute,
Headers: headers,
})
if err != nil {
t.Fatalf("Error bootstrapping developer connection %q: %s", connectionId, err)
}

_, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
Project: config.Project,
RawURL: getURL,
UserAgent: config.UserAgent,
Timeout: 20 * time.Minute,
Headers: headers,
})
if err != nil {
t.Fatalf("Error getting developer connection %q: %s", connectionId, err)
}
}

return connectionId
}

const SharedRepositoryGroupPrefix = "tf-bootstrap-repo-group-"

func BoostrapSharedRepositoryGroup(t *testing.T, repositoryGroupId, location, labels, codeRepositoryIndexId, resource string) string {
repositoryGroupId = SharedRepositoryGroupPrefix + repositoryGroupId

config := BootstrapConfig(t)
if config == nil {
t.Fatal("Could not bootstrap config.")
}

log.Printf("[DEBUG] Getting shared repository group %q", repositoryGroupId)

getURL := fmt.Sprintf("%sprojects/%s/locations/%s/codeRepositoryIndexes/%s/repositoryGroups/%s",
config.GeminiBasePath, config.Project, location, codeRepositoryIndexId, repositoryGroupId)

headers := make(http.Header)
_, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
Project: config.Project,
RawURL: getURL,
UserAgent: config.UserAgent,
Headers: headers,
})
if err != nil {
log.Printf("[DEBUG] Repository group %q not found, bootstrapping", codeRepositoryIndexId)
repositories := [1]interface{}{map[string]string{
"resource": resource,
"branch_pattern": "main",
}}
postURL := fmt.Sprintf("%sprojects/%s/locations/%s/codeRepositoryIndexes/%s/repositoryGroups?repositoryGroupId=%s",
config.GeminiBasePath, config.Project, location, codeRepositoryIndexId, repositoryGroupId)
obj := map[string]interface{}{
"repositories": repositories,
}
if labels != "" {
obj["labels"] = labels
}

headers := make(http.Header)
for {
_, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "POST",
Project: config.Project,
RawURL: postURL,
UserAgent: config.UserAgent,
Body: obj,
Timeout: 20 * time.Minute,
Headers: headers,
})
if err != nil {
if transport_tpg.IsGoogleApiErrorWithCode(err, 409) {
errMsg := fmt.Sprintf("%s", err)
if strings.Contains(errMsg, "unable to queue the operation") {
log.Printf("[DEBUG] Waiting for enqueued operation to finish before creating RepositoryGroup: %#v", obj)
time.Sleep(10 * time.Second)
} else if strings.Contains(errMsg, "parent resource not in ready state") {
log.Printf("[DEBUG] Waiting for parent resource to become active before creating RepositoryGroup: %#v", obj)
time.Sleep(1 * time.Minute)
} else {
t.Fatalf("Error creating RepositoryGroup: %s", err)
}
} else {
t.Fatalf("Error creating repository group %q: %s", repositoryGroupId, err)
}
} else {
break
}
}

_, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
Project: config.Project,
RawURL: getURL,
UserAgent: config.UserAgent,
Timeout: 20 * time.Minute,
Headers: headers,
})
if err != nil {
t.Errorf("Error getting repository group %q: %s", repositoryGroupId, err)
}
}

return repositoryGroupId
}

// BootstrapSharedCodeRepositoryIndex will create a code repository index
// if it hasn't been created in the test project.
//
// BootstrapSharedCodeRepositoryIndex returns a persistent code repository index
// for a test or set of tests.
//
// Deletion of code repository index takes a few minutes, and creation of it
// currently takes about half an hour.
// That is the reason to use the shared code repository indexes for test resources.
const SharedCodeRepositoryIndexPrefix = "tf-bootstrap-cri-"

func BootstrapSharedCodeRepositoryIndex(t *testing.T, codeRepositoryIndexId, location, kmsKey string, labels map[string]string) string {
codeRepositoryIndexId = SharedCodeRepositoryIndexPrefix + codeRepositoryIndexId

config := BootstrapConfig(t)
if config == nil {
t.Fatal("Could not bootstrap config.")
}

log.Printf("[DEBUG] Getting shared code repository index %q", codeRepositoryIndexId)

getURL := fmt.Sprintf("%sprojects/%s/locations/%s/codeRepositoryIndexes/%s", config.GeminiBasePath, config.Project, location, codeRepositoryIndexId)

headers := make(http.Header)
_, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
Project: config.Project,
RawURL: getURL,
UserAgent: config.UserAgent,
Timeout: 90 * time.Minute,
Headers: headers,
})

// CRI not found responds with 404 not found
if err != nil && transport_tpg.IsGoogleApiErrorWithCode(err, 404) {
log.Printf("[DEBUG] Code repository index %q not found, bootstrapping", codeRepositoryIndexId)
postURL := fmt.Sprintf("%sprojects/%s/locations/%s/codeRepositoryIndexes?codeRepositoryIndexId=%s", config.GeminiBasePath, config.Project, location, codeRepositoryIndexId)
obj := make(map[string]interface{})
if labels != nil {
obj["labels"] = labels
}
if kmsKey != "" {
obj["kmsKey"] = kmsKey
}

headers := make(http.Header)
_, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "POST",
Project: config.Project,
RawURL: postURL,
UserAgent: config.UserAgent,
Body: obj,
Timeout: 90 * time.Minute,
Headers: headers,
})
if err != nil {
t.Fatalf("Error creating code repository index %q: %s", codeRepositoryIndexId, err)
}

_, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
Project: config.Project,
RawURL: getURL,
UserAgent: config.UserAgent,
Timeout: 90 * time.Minute,
Headers: headers,
})
if err != nil {
t.Fatalf("Error getting code repository index %q: %s", codeRepositoryIndexId, err)
}
} else if err != nil {
t.Fatalf("Error getting code repository index %q: %s", codeRepositoryIndexId, err)
}

return codeRepositoryIndexId
}

const sharedTagKeyPrefix = "tf-bootstrap-tagkey"

func BootstrapSharedTestTagKey(t *testing.T, testId string) string {
Expand Down
11 changes: 8 additions & 3 deletions google-beta/provider/provider_mmv1_resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ var generatedIAMDatasources = map[string]*schema.Resource{
"google_dataproc_metastore_federation_iam_policy": tpgiamresource.DataSourceIamPolicy(dataprocmetastore.DataprocMetastoreFederationIamSchema, dataprocmetastore.DataprocMetastoreFederationIamUpdaterProducer),
"google_dataproc_metastore_service_iam_policy": tpgiamresource.DataSourceIamPolicy(dataprocmetastore.DataprocMetastoreServiceIamSchema, dataprocmetastore.DataprocMetastoreServiceIamUpdaterProducer),
"google_dns_managed_zone_iam_policy": tpgiamresource.DataSourceIamPolicy(dns.DNSManagedZoneIamSchema, dns.DNSManagedZoneIamUpdaterProducer),
"google_gemini_repository_group_iam_policy": tpgiamresource.DataSourceIamPolicy(gemini.GeminiRepositoryGroupIamSchema, gemini.GeminiRepositoryGroupIamUpdaterProducer),
"google_gke_backup_backup_plan_iam_policy": tpgiamresource.DataSourceIamPolicy(gkebackup.GKEBackupBackupPlanIamSchema, gkebackup.GKEBackupBackupPlanIamUpdaterProducer),
"google_gke_backup_restore_plan_iam_policy": tpgiamresource.DataSourceIamPolicy(gkebackup.GKEBackupRestorePlanIamSchema, gkebackup.GKEBackupRestorePlanIamUpdaterProducer),
"google_gke_hub_membership_iam_policy": tpgiamresource.DataSourceIamPolicy(gkehub.GKEHubMembershipIamSchema, gkehub.GKEHubMembershipIamUpdaterProducer),
Expand Down Expand Up @@ -508,9 +509,9 @@ var handwrittenIAMDatasources = map[string]*schema.Resource{
}

// Resources
// Generated resources: 563
// Generated IAM resources: 291
// Total generated resources: 854
// Generated resources: 564
// Generated IAM resources: 294
// Total generated resources: 858
var generatedResources = map[string]*schema.Resource{
"google_folder_access_approval_settings": accessapproval.ResourceAccessApprovalFolderSettings(),
"google_organization_access_approval_settings": accessapproval.ResourceAccessApprovalOrganizationSettings(),
Expand Down Expand Up @@ -978,6 +979,10 @@ var generatedResources = map[string]*schema.Resource{
"google_firestore_field": firestore.ResourceFirestoreField(),
"google_firestore_index": firestore.ResourceFirestoreIndex(),
"google_gemini_code_repository_index": gemini.ResourceGeminiCodeRepositoryIndex(),
"google_gemini_repository_group": gemini.ResourceGeminiRepositoryGroup(),
"google_gemini_repository_group_iam_binding": tpgiamresource.ResourceIamBinding(gemini.GeminiRepositoryGroupIamSchema, gemini.GeminiRepositoryGroupIamUpdaterProducer, gemini.GeminiRepositoryGroupIdParseFunc),
"google_gemini_repository_group_iam_member": tpgiamresource.ResourceIamMember(gemini.GeminiRepositoryGroupIamSchema, gemini.GeminiRepositoryGroupIamUpdaterProducer, gemini.GeminiRepositoryGroupIdParseFunc),
"google_gemini_repository_group_iam_policy": tpgiamresource.ResourceIamPolicy(gemini.GeminiRepositoryGroupIamSchema, gemini.GeminiRepositoryGroupIamUpdaterProducer, gemini.GeminiRepositoryGroupIdParseFunc),
"google_gke_backup_backup_plan": gkebackup.ResourceGKEBackupBackupPlan(),
"google_gke_backup_backup_plan_iam_binding": tpgiamresource.ResourceIamBinding(gkebackup.GKEBackupBackupPlanIamSchema, gkebackup.GKEBackupBackupPlanIamUpdaterProducer, gkebackup.GKEBackupBackupPlanIdParseFunc),
"google_gke_backup_backup_plan_iam_member": tpgiamresource.ResourceIamMember(gkebackup.GKEBackupBackupPlanIamSchema, gkebackup.GKEBackupBackupPlanIamUpdaterProducer, gkebackup.GKEBackupBackupPlanIdParseFunc),
Expand Down
11 changes: 6 additions & 5 deletions google-beta/services/gemini/gemini_operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@ func (w *GeminiOperationWaiter) QueryOp() (interface{}, error) {
url := fmt.Sprintf("%s%s", w.Config.GeminiBasePath, w.CommonOperationWaiter.Op.Name)

return transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: w.Config,
Method: "GET",
Project: w.Project,
RawURL: url,
UserAgent: w.UserAgent,
Config: w.Config,
Method: "GET",
Project: w.Project,
RawURL: url,
UserAgent: w.UserAgent,
ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{transport_tpg.IsCodeRepositoryIndexUnreadyError, transport_tpg.IsRepositoryGroupQueueError},
})
}

Expand Down
Loading

0 comments on commit ab3ebdf

Please sign in to comment.