{"id":28929558,"url":"https://github.com/dubinsky/pulumi","last_synced_at":"2026-05-02T10:43:14.881Z","repository":{"id":189054903,"uuid":"679919020","full_name":"dubinsky/pulumi","owner":"dubinsky","description":null,"archived":false,"fork":false,"pushed_at":"2025-07-21T21:01:46.000Z","size":251,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-21T23:11:49.405Z","etag":null,"topics":["gcp","gradle","pulumi","scala","terraform"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dubinsky.png","metadata":{"files":{"readme":"README-terraform.adoc","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-08-17T23:31:15.000Z","updated_at":"2025-07-21T21:01:50.000Z","dependencies_parsed_at":null,"dependency_job_id":"f18fbd8a-482c-4b58-8de4-125a63f6cd2a","html_url":"https://github.com/dubinsky/pulumi","commit_stats":null,"previous_names":["dubinsky/pulumi"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dubinsky/pulumi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dubinsky%2Fpulumi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dubinsky%2Fpulumi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dubinsky%2Fpulumi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dubinsky%2Fpulumi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dubinsky","download_url":"https://codeload.github.com/dubinsky/pulumi/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dubinsky%2Fpulumi/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269931473,"owners_count":24498722,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-11T02:00:10.019Z","response_time":75,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gcp","gradle","pulumi","scala","terraform"],"created_at":"2025-06-22T14:31:40.097Z","updated_at":"2026-05-02T10:43:14.854Z","avatar_url":"https://github.com/dubinsky.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"== Google Workspace\n\nUnlike Pulumi, Terraform has a Google Workspace provider, which can be used\nto manage:\n- creation and deletion of workspace users\n- settings for workspace users\n- group settings and _aliases_\n\nIf using Google Workspace Terraform provider to manage users and groups,\nassign \"User Management Admin\" and \"Group Admin\" roles to\nthe Terraform service account `terraform@domain-infra.iam.gserviceaccount.com`\nin https://admin.google.com/ac/roles[Admin Console].\n\nIn the sample Terraform files, fragments that rely on the Google Workspace provider\nare commented out and marked with `using Workspace provider`.\n\n== Command Line Tools\n\n`Terraform` - from https://learn.hashicorp.com/tutorials/terraform/install-cli[hashicorp.com] (or `google-cloud-cli-terraform-tools`).\n\n== Setup\n\nSample Terraform files are in the link:domain-infra/terraform[domain-infra/terraform] folder.\n\nNo additional setup is needed - just run `terraform` command in that folder.\n\nLooping approach using `for_each` borrowed from a https://blog.gruntwork.io/terraform-tips-tricks-loops-if-statements-and-gotchas-f739bbae55f9[blog post]\nby https://medium.com/@brikis98[Yevgeniy Brikman].\n\nSample files:\n\n- link:domain-infra/terraform/terraform/.gitignore[.gitignore] - do not check the state in\n- link:domain-infra/terraform/terraform/main.tf[main.tf] - overall setup\n- link:domain-infra/terraform/terraform/project-infra.tf[project-infra.tf] - project and its services\n- link:domain-infra/terraform/terraform/sa-terraform.tf[sa-terraform.tf] - service account and its roles\n- link:domain-infra/terraform/terraform/group-gcp-organization-admins.tf[group-gcp-organization-admins.tf] - administrators group and its roles\n- link:domain-infra/terraform/terraform/user-admin.tf[user-admin.tf] - administrator\n- link:domain-infra/terraform/terraform/bucket-state.domain.tld.tf[bucket-state.domain.tld.tf] - bucket to store state\n\nIn `main.tf`, we specify the Google Cloud Storage bucket to use to store Terraform state -\nuntil the state migrates into the bucket, those lines need to be commented out.\n\nOn a local machine, we use `.envrc` file in the project repository\nthat `direnv` processes to set the appropriate environment variables;\nsee link:domain-infra/terraform/.envrc[.envrc].\n\n== Initialize, Import and Migrate State\n\nNow we are ready to initialize Terraform:\n\n[source,shell]\n$ cd terraform\n$ terraform init\n\nExisting Google Cloud Platform resources can be bulk-exported in Terraform format if desired:\n[source,shell]\n$ gcloud beta resource-config bulk-export --path=entire-tf-output \\\n  --organization=org_id --resource-format=terraform\n\nNow, we import existing resources:\n\n[source,shell]\n----\n# project\n$ terraform import google_project.infra \"projects/domain-infra\"\n\n# service account\n$ terraform import google_service_account.terraform \\\n  \"projects/domain-infra/serviceAccounts/terraform@domain-infra.iam.gserviceaccount.com\"\n\n# if using Workspace provider to manage Google Workspace user(s)\n$ terraform import googleworkspace_user.admin admin@domain.tld\n----\n\nInstead of importing enabled services of the infrastructure project individually like this:\n[source,shell]\n$ terraform import google_project_service.cloudbilling_googleapis_com \\\n  domain-infra/cloudbilling.googleapis.com\n\nI rely on the idempotency and just Terraform the whole\nmap `google_project_service.project[\"...\"]` over;\nas a result, initial `terraform apply` might fail\nand will need to be repeated - depending on the order of modifications.\nThe same applies to the service account roles.\n\nNow, the state described by the state is applied:\n[source,shell]\n$ terraform apply\n\nNow that the state bucket exists, we migrate the state into it:\n\nIn `main.tf`, uncomment `backend \"gcs\" {...}`.\nThen, move the state to the bucket (see https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/data-sources/remote_state[documentation]):\n[source,shell]\n$ terraform init -migrate-state\n\n== Failure to bootstrap for Google Workspace\nTo remove one more UI-based step,\nI tried to use Terraform to assign\n_GROUPS_ADMIN_ROLE and _USER_MANAGEMENT_ADMIN_ROLE roles\nto the Terraform Service Account;\neven if it worked, it is probably easier to use the Admin Console - but it didn't work:\n\n[source,shell]\n----\n$ gcloud auth application-default login \\\n  --scopes \"https://www.googleapis.com/auth/admin.directory.rolemanagement\"\n----\nresults in:\n[source,text]\n----\nThis app is blocked\nThis app tried to access sensitive info in your Google Account.\nTo keep your account safe, Google blocked this access.\n----\nand `terraform apply` (with all the scopes enabled in the Google Workspace provider!) of\n[source,terraform]\n----\ndata \"googleworkspace_role\" \"groups-admin\" {\n  name = \"_GROUPS_ADMIN_ROLE\"\n}\nresource \"googleworkspace_role_assignment\" \"terraform-groups-admin\" {\n  role_id     = data.googleworkspace_role.groups-admin.id\n  assigned_to = google_service_account.terraform.unique_id\n  scope_type  = \"CUSTOMER\"\n}\ndata \"googleworkspace_role\" \"user-management-admin\" {\n  name = \"_USER_MANAGEMENT_ADMIN_ROLE\"\n}\nresource \"googleworkspace_role_assignment\" \"terraform-user-management-admin\" {\n  role_id     = data.googleworkspace_role.user-management-admin.id\n  assigned_to = google_service_account.terraform.unique_id\n  scope_type  = \"CUSTOMER\"\n}\n----\nresults in:\n[source,text]\n----\nError: googleapi: Error 403: Request had insufficient authentication scopes.\nDetails:\n[{\n  \"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\",\n  \"domain\": \"googleapis.com\",\n  \"metadata\": {\n    \"method\": \"ccc.hosted.frontend.directory.v1.DirectoryRoles.List\",\n    \"service\": \"admin.googleapis.com\"\n  },\n  \"reason\": \"ACCESS_TOKEN_SCOPE_INSUFFICIENT\"\n}]\nInsufficient Permission ... in data \"googleworkspace_role\" \"groups-admin\"\n----\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdubinsky%2Fpulumi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdubinsky%2Fpulumi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdubinsky%2Fpulumi/lists"}