diff --git a/.agents/WORKFLOWS.md b/.agents/WORKFLOWS.md index e511c698bcf2..1c8f809f9c7d 100644 --- a/.agents/WORKFLOWS.md +++ b/.agents/WORKFLOWS.md @@ -21,6 +21,8 @@ specific one. | `new-resource` | New Resource Creation | `.agents/skills/workflows/new_resource/SKILL.md` | | `list-resource` | List Resource Addition | `.agents/skills/workflows/add_list_resource/SKILL.md` | | `enhancement` (field additions) | New Field / Property Addition | `.agents/skills/workflows/add_fields/SKILL.md` | +| `deprecation` | Deprecation Notice Addition | `.agents/skills/workflows/deprecate_resource_or_field/SKILL.md` | +| `breaking-change` | Major Release Removal / Breaking Change | `.agents/skills/workflows/major_release_removal/SKILL.md` | | *No matching label / un-labeled* | General Modification / Bug Fix | Fallback to `.agents/skills/workflows/default/SKILL.md` (or inspect issue body) | ## Available Workflows: @@ -34,8 +36,11 @@ specific one. * **Test Monitor Workflow** (`.agents/skills/workflows/test_monitor/SKILL.md`): For fetching, triaging, analyzing, and reporting on nightly acceptance test results across Beta and GA providers. * **Add IAM Support Workflow** (`.agents/skills/workflows/add_iam_resources/SKILL.md`): Adds IAM support to an existing MMv1 resource. * **Bug Fix Workflow** (`.agents/skills/workflows/bug_fix/SKILL.md`): For triaging, planning, fixing, and verifying reported provider bugs. +* **Deprecate Resource or Field Workflow** (`.agents/skills/workflows/deprecate_resource_or_field/SKILL.md`): Specifically for deprecating existing resources or fields on `main` prior to a major release. +* **Major Release Removal Workflow** (`.agents/skills/workflows/major_release_removal/SKILL.md`): Specifically for executing resource and field removals on a major release feature branch. * *(Future workflows can be added here)* ## Subagents: * **`test-fixer`** (`.agents/agents/test-fixer/`): Automates diagnosis, remediation in Magic Modules, provider generation, and re-testing for failing acceptance tests. +* **`removal-auditor`** (`.agents/agents/removal-auditor/`): Audits deprecation status on `main`, sync status on the major release branch, and scans repository dependencies for resource and field removals. diff --git a/.agents/agents/removal-auditor/agent.json b/.agents/agents/removal-auditor/agent.json new file mode 100644 index 000000000000..eb6c10745a53 --- /dev/null +++ b/.agents/agents/removal-auditor/agent.json @@ -0,0 +1,6 @@ +{ + "name": "removal-auditor", + "configPath": { + "relativePathToConfig": "config.yaml" + } +} diff --git a/.agents/agents/removal-auditor/config.yaml b/.agents/agents/removal-auditor/config.yaml new file mode 100644 index 000000000000..c76aa48fd09b --- /dev/null +++ b/.agents/agents/removal-auditor/config.yaml @@ -0,0 +1,68 @@ +custom_agent: + system_prompt_sections: + - title: "Core Instructions" + content: | + Your task is to perform an end-to-end pre-flight audit for a planned resource or field removal for a major release. + + Input Target: + - `product`: Product directory name (e.g. `compute`, `dlp`, `notebooks`, `vertexai`). + - `resource`: Resource name (e.g. `JobTrigger`, `google_notebooks_instance`, `Schedule`). + - `field` (Optional): Specific field name (e.g. `publish_findings_to_cloud_data_catalog`). If omitted or set to `all`, treat as a full resource removal. + - `major_release_branch`: Major release branch (e.g. `FEATURE-BRANCH-major-release-8.0.0`). + + Follow these audit steps: + + 1. **Pre-Flight `main` Branch Audit**: + - Check the state of the target resource or field on `upstream/main`. + - **Deprecation Check**: Verify whether `deprecation_message` (for MMv1 YAML) or `Deprecated` / `DeprecationMessage` (for handwritten Go) is set on `upstream/main`. + - **Replacement Availability**: If the field/resource is being replaced (e.g., in favor of another field or resource), verify that the replacement is fully implemented and available on `upstream/main`. + - **Forward Compatibility**: If a required field is slated for removal, verify whether it was already changed to optional on `upstream/main`. + - If `main` is missing the deprecation or replacement, flag this immediately as a **BLOCKER: Requires Deprecation on `main` First**. + + 2. **Major Release Branch Sync Audit**: + - Check `upstream/` to verify whether the deprecation and replacement commits from `main` have been merged into the major release branch. + - If the major release branch is missing the deprecation commits from `main`, flag this as a **BLOCKER: Needs Main Sync**. + + 3. **Comprehensive Dependency & Blast Radius Scan**: + Search across the codebase for all references to the resource or field: + - **MMv1 Product & Resource YAMLs**: `mmv1/products//...` + - **Custom Code Templates**: `mmv1/templates/terraform/` (e.g., `constants/`, `custom_check_destroy/`, `custom_expand/`, `custom_flatten/`, `encoder/`, `update_encoder/`, `pre_create/`, `post_create/`, `pre_update/`, `post_update/`, `state_migrations/`). + - **Samples & Examples**: `mmv1/templates/terraform/samples/services//` and `mmv1/templates/terraform/examples/`. + - **Handwritten Code & Tests**: `mmv1/third_party/terraform/services//...` (including `ImportStateVerifyIgnore` entries). + - **Issue Labeler**: `tools/issue-labeler/labeler/enrolled_teams.yml`. + - **TGC Converters**: `mmv1/third_party/tgc/resource_converters.go.tmpl` (converter mappings and IAM registrations). + - **Documentation**: `mmv1/third_party/terraform/website/docs/...` + - **Cross-Resource Links**: Any other product YAMLs or test fixtures referencing the target. + + 4. **Compile Removal Audit Report Artifact**: + Generate a structured Markdown report detailing: + - Target summary (Product, Resource, Field, Scope: MMv1 vs. Handwritten, Resource vs. Field). + - Pre-flight status on `main` (Deprecation message present? Replacement ready?). + - Sync status on the major release branch. + - Complete file modification list (files to delete, files to edit, exact sections). + - Acceptance tests requiring updates or execution. + - Proposed Upgrade Guide entry text for `version__upgrade.html.markdown`. + + - title: "Report Format" + content: | + You must return a clear Markdown audit report with the following structure: + - **Target Summary**: Target name, type (MMv1 vs Handwritten), removal scope (Field vs Resource vs Entire Product). + - **Pre-Flight Readiness**: + - `main` branch deprecation status: `READY` or `BLOCKED` (with explanation). + - Major release branch sync status: `SYNCED` or `NEEDS_SYNC`. + - **Actionable Removal Plan**: + - Files to delete. + - Files to edit (with line numbers / property names). + - Test adjustments (`ImportStateVerifyIgnore`, sample deletions, test fixture updates). + - Issue labeler adjustments (`tools/issue-labeler/labeler/enrolled_teams.yml`). + - TGC converter cleanup requirements. + - **Proposed Upgrade Guide Entry**: Draft markdown text ready to insert into `version__upgrade.html.markdown`. + + tool_names: + - view_file + - list_dir + - run_command + - grep_search + - write_to_file + +command_execution_policy: "eager" diff --git a/.agents/knowledge/index.md b/.agents/knowledge/index.md index 5430b7c223f5..8157b40aa648 100644 --- a/.agents/knowledge/index.md +++ b/.agents/knowledge/index.md @@ -37,6 +37,8 @@ Read this index at decision points; open only the source the task needs. Format - Create a PR — `docs/content/code-review/create-pr.md` - Write release notes — `docs/content/code-review/release-notes.md` - Review a PR — `docs/content/code-review/review-pr.md` +- Make a breaking change / deprecations / removals — `docs/content/breaking-changes/make-a-breaking-change.md` +- Types of breaking changes — `docs/content/breaking-changes/breaking-changes.md` **Documentation:** diff --git a/.agents/skills/operations/create-pr/SKILL.md b/.agents/skills/operations/create-pr/SKILL.md index a54e76bcdcd2..0758efd0c889 100644 --- a/.agents/skills/operations/create-pr/SKILL.md +++ b/.agents/skills/operations/create-pr/SKILL.md @@ -7,14 +7,13 @@ description: "Create a Pull Request (PR) against GoogleCloudPlatform/magic-modul > **Note to AI Agents:** You MUST read the YAML frontmatter above first. Only read the rest of this file if the `description` matches your current roadblock or required task. -This skill provides step-by-step instructions for preparing, formatting, and opening a Pull Request (PR) for `magic-modules` following the official contribution guidelines. +This skill provides step-by-step instructions for preparing, formatting, and opening a Pull Request (PR) for `magic-modules` following official contribution guidelines. ## Prerequisites * You are in the `magic-modules` root directory. * Your git working directory is clean except for the files intended for the PR. * Downstream provider changes are NOT staged or committed to `magic-modules`. -* `gh` CLI is installed and authenticated (`gh auth status`). * Remote repositories are configured (e.g., `upstream` pointing to `GoogleCloudPlatform/magic-modules` and a personal fork remote such as `origin`). --- @@ -26,11 +25,12 @@ Before creating a branch or opening a PR, verify all of the following rules: 1. **Single Self-Contained Change:** Each PR must contain only **one** logical change. - Adding multiple resources? Put **one resource per PR**. - Fixing a bug and adding new fields? Split into **two separate PRs**. -2. **No Downstream Artifacts in Magic Modules:** - - Do NOT commit generated downstream provider code (e.g. `$GOPATH/src/github.com/hashicorp/terraform-provider-google`) into `magic-modules`. -3. **Plan Completeness:** Verify that every file listed in the implementation plan (including any necessary documentation) has been generated and staged. -4. **Pre-Gen Static Checks:** Execute the `run-pre-gen-checks` skill (`.agents/skills/utils/run-pre-gen-checks/`) to run fast static checks directly against Magic Modules (Go formatting, YAML linting, template validation, and MMv1 unit tests). -5. **Workspace Cleanup:** Run `git status --porcelain` and remove any untracked `.log`, `.test`, or temporary test artifacts across both `magic-modules` and downstream repositories before opening the PR. +2. **Strict PR Title Length Limit:** + - PR title must be concise and strictly **under 70 characters**. + - Format: `: ` (e.g. `beyondcorp: deprecate google_beyondcorp_app_*` or `compute: add foo field to google_compute_instance`). +3. **No Downstream Artifacts in Magic Modules:** + - Do NOT commit generated downstream provider code into `magic-modules`. +4. **Workspace Cleanup:** Run `git status --porcelain` and ensure no untracked temporary test files exist before opening the PR. --- @@ -38,37 +38,25 @@ Before creating a branch or opening a PR, verify all of the following rules: ### 1. Sync and Create Feature Branch -Identify the upstream remote (pointing to `GoogleCloudPlatform/magic-modules`), fetch `main`, and create a clean topic branch: - ```bash -# Discover the remote for GoogleCloudPlatform/magic-modules (defaults to 'upstream' if unmatched) UPSTREAM_REMOTE=$(git remote -v | grep -i "GoogleCloudPlatform/magic-modules" | head -n 1 | awk '{print $1}') UPSTREAM_REMOTE="${UPSTREAM_REMOTE:-upstream}" git fetch "$UPSTREAM_REMOTE" main -BRANCH="" # e.g. add-compute-foo-field +BRANCH="" # e.g. deprecate-beyondcorp-app git checkout -b "$BRANCH" "$UPSTREAM_REMOTE/main" ``` ### 2. Stage and Commit Changes -Stage ONLY the files in `magic-modules` (such as YAML definitions under `mmv1/products/`, templates, or docs): - ```bash -git add mmv1/products// -git commit -m ": " +git add mmv1/products// # or other relevant files +git commit -m ": " ``` -### 3. Push to Fork - -Identify your personal fork remote. If multiple non-upstream remotes exist or the target remote is ambiguous, stop and ask the user which remote to push to: +### 3. Push to Personal Fork ```bash -# Check available remotes -git remote -v - -# Automatically select personal fork remote (remote not pointing to GoogleCloudPlatform). -# If multiple non-upstream remotes exist, ask the user to confirm the remote name. FORK_REMOTE=$(git remote -v | grep -v -i "GoogleCloudPlatform/magic-modules" | head -n 1 | awk '{print $1}') FORK_REMOTE="${FORK_REMOTE:-origin}" @@ -77,78 +65,75 @@ git push -u "$FORK_REMOTE" "$BRANCH" --- -### 4. Format Release Notes & PR Body - -Every PR must contain at least one release note block in the PR body. +### 4. Format PR Body & Release Notes -Refer to the official guide for detailed release note rules, categories, and examples: -* [docs/content/code-review/release-notes.md](../../../../docs/content/code-review/release-notes.md) +Every PR must contain a clear summary and a release note block in the PR body. -> [!IMPORTANT] -> **Note to AI Agents:** View `docs/content/code-review/release-notes.md` from the repository root to determine the correct release note type (e.g., `enhancement`, `bug`, `none`, `new-resource`, `deprecation`, etc.) and follow the end-user impact guidelines. +Refer to [docs/content/code-review/release-notes.md](../../../../docs/content/code-review/release-notes.md) for details on categories (`enhancement`, `bug`, `none`, `new-resource`, `deprecation`, `breaking-change`). -#### Release Note Block Format -```markdown -```release-note:TYPE -CONTENT -``` -``` - -#### Sample PR Body Content +#### Sample PR Body ```markdown Summary of what changed and why in a few concise sentences. -Modeled after: https://github.com/GoogleCloudPlatform/magic-modules/pull/12344 Fixes https://github.com/hashicorp/terraform-provider-google/issues/12345 ```release-note:enhancement -compute: added `foo` field to `google_compute_instance` resource +: added `foo` field to `google_compute_instance` ``` ``` --- -### 5. Create Pull Request with `gh` CLI Using `--body-file` - -> [!CAUTION] -> **DO NOT pass inline double-quoted `--body "..."` strings containing backticks.** -> Enclosing triple backticks (` ```release-note:type ``` `) in double quotes causes `zsh`/`bash` to execute `` `release-note:type` `` as a live shell command substitution. The command fails, silently stripping the release note block from the published PR body! - -* **Upstream Target:** Most likely target `--repo GoogleCloudPlatform/magic-modules` (upstream magic modules), and when in doubt ask the user. -* **PR Title Length:** Keep the PR title strictly under 60 characters, ideally < 50 characters (format: `: `). +### 5. Create PR or Provide Pre-Filled Web Link -Always write the body to a temporary file via a single-quoted HEREDOC (`cat <<'EOF'`) and invoke `gh pr create` with `--body-file`: +Write the PR body to a temporary file (`/tmp/pr_body.txt`) using a single-quoted HEREDOC: ```bash -PR_TITLE=": " # e.g. compute: add foo field to google_compute_instance (under 60 chars) +PR_TITLE=": " +BASE_BRANCH="main" # or FEATURE-BRANCH-major-release-8.0.0 cat <<'EOF' > /tmp/pr_body.txt +Fixes + ```release-note: - +: ``` EOF +``` +#### Attempt `gh pr create`: +```bash gh pr create \ --repo GoogleCloudPlatform/magic-modules \ - --base main \ + --base "$BASE_BRANCH" \ --head "$(gh api user -q .login):$BRANCH" \ --title "$PR_TITLE" \ --body-file /tmp/pr_body.txt ``` +#### Generate Pre-Filled Web URL (Always Provide as Hyperlink): +Always generate and present a clickable markdown link with the PR title and description pre-filled in query parameters for easy user review and submission: + +```python +import urllib.parse + +base_branch = "main" # or "FEATURE-BRANCH-major-release-8.0.0" +head_ref = f"{username}:{branch}" # e.g. "c2thorn:deprecate-beyondcorp-app" +title = "..." +body = "..." + +url = f"https://github.com/GoogleCloudPlatform/magic-modules/compare/{base_branch}...{head_ref}?expand=1&title={urllib.parse.quote(title)}&body={urllib.parse.quote(body)}" +print(url) +``` + +Present the result in chat as: +👉 **[Create Pull Request on GitHub]()** + --- -## Verification & Auto-Repair Handoff - -1. Verify that `gh pr create` completed successfully and returned a valid Pull Request URL. -2. **Verify PR Body Integrity:** Run `gh pr view` to verify the published body rendered the release note block: - ```bash - gh pr view --repo GoogleCloudPlatform/magic-modules - ``` -3. **Auto-Repair Missing Release Note:** If the output body does NOT contain `release-note:`, repair it immediately: - ```bash - gh pr edit --repo GoogleCloudPlatform/magic-modules --body-file /tmp/pr_body.txt - ``` -4. Share the confirmed PR URL with the user and complete the execution. +## Verification & Summary + +1. If `gh pr create` succeeds, view the published PR: `gh pr view --repo GoogleCloudPlatform/magic-modules`. +2. Share the confirmed PR URL (or the pre-filled direct compare URL) with the user. diff --git a/.agents/skills/workflows/deprecate_resource_or_field/SKILL.md b/.agents/skills/workflows/deprecate_resource_or_field/SKILL.md new file mode 100644 index 000000000000..24ebefa7e04f --- /dev/null +++ b/.agents/skills/workflows/deprecate_resource_or_field/SKILL.md @@ -0,0 +1,64 @@ +--- +name: deprecate-resource-or-field-workflow +description: "Workflow for deprecating existing resources, data sources, or fields on the main branch prior to a major release." +--- + +# `deprecate-resource-or-field-workflow` + +> **Note to AI Agents:** You MUST read the YAML frontmatter above first. Only read the rest of this file if the `description` matches your required task. + +This workflow governs adding deprecation notices and establishing forwards-compatibility on the `main` branch prior to a major release. + +## Prerequisites + +- You must be operating on the `main` branch in the `magic-modules` root directory. +- You must know the target resource/data source (e.g., `google_beyondcorp_app_connector`) and the field (or entire resource/data source) to deprecate. +- You must know the replacement path (if applicable) or the reason for deprecation. + +--- + +## Execution Steps + +### 1. Context & Guidance + +Before beginning implementation, consult: +- `docs/content/breaking-changes/make-a-breaking-change.md` — Section *Add deprecations and warnings to the main branch*. +- `docs/content/reference/field.md` and `docs/content/reference/resource.md` — Configuration references. +- `.agents/knowledge/index.md` — General knowledge index. + +### 2. Forwards-Compatibility & Replacement + +- **Required Fields**: If a required field is being deprecated for future removal, make it optional (`required: true` $\rightarrow$ `optional: true` in MMv1 YAML or `Required: false, Optional: true` in Go) so configurations can begin omitting it prior to the major release. +- **Renames / Replacements**: Ensure the replacement field or resource is implemented, tested, and available on `main` before or alongside the deprecation. + +### 3. Apply Deprecation Notice + +Follow the conventions in `docs/content/breaking-changes/make-a-breaking-change.md`: +- **MMv1 Resources/Fields**: Set `deprecation_message` on the target property or resource in `mmv1/products//.yaml`. +- **Handwritten Resources/Data Sources**: Set `Deprecated` (fields) or `DeprecationMessage` (resources / data sources) in Go schema (`mmv1/third_party/terraform/services//...`) and add deprecation warnings to markdown documentation in `website/docs/r/` or `website/docs/d/`. + +### 4. Run Pre-Gen Checks + +- Execute [run-pre-gen-checks](.agents/skills/utils/run-pre-gen-checks/SKILL.md) to verify Go formatting, YAML linting, template validation, and unit tests. + +### 5. Generate Provider & Verify Build + +- Execute [generate-provider](.agents/skills/operations/generate-provider/SKILL.md). +- Run `make build` in the downstream provider repository to verify compilation. + +### 6. Verification Testing + +- Invoke [qa-test-runner](.agents/skills/operations/qa-test-runner/SKILL.md) to verify acceptance tests pass (`PASS`). + +### 7. PR Creation & Release Note + +Execute [create-pr](.agents/skills/operations/create-pr/SKILL.md) targeting `main`: +- **Title Length Limit**: Must be strictly **under 70 characters** (e.g. `: deprecate `). +- **Body**: + ```markdown + ```release-note:deprecation + : deprecated `` on `google_`. Use `` instead. + ``` + *(or `: deprecated `google_` resource/data source`)* + ``` +- **Pre-Filled Hyperlink**: Always generate and provide a pre-filled markdown compare URL in chat for easy user submission. diff --git a/.agents/skills/workflows/major_release_removal/SKILL.md b/.agents/skills/workflows/major_release_removal/SKILL.md new file mode 100644 index 000000000000..0cc111cca43c --- /dev/null +++ b/.agents/skills/workflows/major_release_removal/SKILL.md @@ -0,0 +1,110 @@ +--- +name: major-release-removal-workflow +description: "Workflow for executing resource, data source, and field removals on a major release feature branch." +--- + +# `major-release-removal-workflow` + +> **Note to AI Agents:** You MUST read the YAML frontmatter above first. Only read the rest of this file if the `description` matches your required task. + +This workflow governs removing deprecated resources, data sources, or fields on a major release feature branch (e.g., `FEATURE-BRANCH-major-release-8.0.0`). + +--- + +## Prerequisites + +- You must be operating in the `magic-modules` repository root directory. +- Determine the target major release version at the start: + - `MAJOR_VERSION`: (e.g. `8.0.0`) + - `FEATURE_BRANCH="FEATURE-BRANCH-major-release-${MAJOR_VERSION}"` + - `UPGRADE_GUIDE="mmv1/third_party/terraform/website/docs/guides/version_${MAJOR_VERSION%%.*}_upgrade.html.markdown"` +- You must know the target product, resource/data source, and field (or full resource/data source) to remove. + +--- + +## Execution Steps + +### 1. Pre-Flight Audit via `removal-auditor` Subagent + +Launch the `removal-auditor` subagent (`.agents/agents/removal-auditor/`) with the target product, resource/data source, field, and `FEATURE_BRANCH`. + +The subagent will inspect `upstream/main` (deprecation & replacement checks), `upstream/${FEATURE_BRANCH}` (sync status), and all repo dependencies, returning a **Removal Audit & Blast-Radius Report**. + +#### Gating Checks: +- **If deprecation is missing on `main`**: + - Present the blocker to the user. + - If user directs staging both changes in the session, first execute [deprecate-resource-or-field-workflow](../deprecate_resource_or_field/SKILL.md) on `main`, push the branch, and then proceed with the removal on `${FEATURE_BRANCH}`. +- **If the release branch is not synced with `main`**: Stop this workflow. Invoke [`sync-main-to-major-release-branch`](file:///usr/local/google/home/camthornton/.gemini/config/skills/sync-main-to-major-release-branch/SKILL.md) first. + +--- + +### 2. Plan Presentation & Review Checkpoint + +Present the subagent's audit report and proposed removal plan to the user as an artifact (schema deletions, template/sample/test cleanup, issue labeler entries, TGC converters, and proposed upgrade guide text). + +**Do NOT proceed to code modifications until the user approves the plan.** + +--- + +### 3. Checkout Major Release Branch Across Workspaces + +1. Create a local working branch in `magic-modules` based on `upstream/${FEATURE_BRANCH}`: + ```bash + git checkout -b remove- upstream/$FEATURE_BRANCH + ``` +2. Ensure downstream provider repositories (`$GOPATH/src/github.com/hashicorp/terraform-provider-google` and `terraform-provider-google-beta`) are checked out on `$FEATURE_BRANCH`: + ```bash + git -C $GOPATH/src/github.com/hashicorp/terraform-provider-google checkout -B $FEATURE_BRANCH origin/$FEATURE_BRANCH + git -C $GOPATH/src/github.com/hashicorp/terraform-provider-google-beta checkout -B $FEATURE_BRANCH origin/$FEATURE_BRANCH + ``` + +--- + +### 4. Code Removal & Cleanup + +Execute removals across the exact files identified in the approved Removal Audit Report: +- **Schema**: Delete property/resource YAML in `mmv1/products//...` or handwritten Go schema / data sources (`mmv1/third_party/terraform/services//...`). +- **Custom Templates & Hooks**: Remove associated templates in `mmv1/templates/terraform/` (expanders, flatteners, hooks, constants, state migrations under `state_migrations/`). +- **Samples & Examples**: Remove obsolete sample `.tf.tmpl` files and test configs. +- **Acceptance Tests**: Remove deleted fields from test configs and `ImportStateVerifyIgnore` slices; delete test files for removed resources / data sources. +- **Issue Labeler**: Remove resource mapping from `tools/issue-labeler/labeler/enrolled_teams.yml` if the entire resource was removed. +- **TGC Converters**: Remove converter mappings and IAM registrations from `mmv1/third_party/tgc/resource_converters.go.tmpl`. +- **Documentation**: Remove handwritten documentation markdown (`docs/r/` or `docs/d/`) if applicable. + +--- + +### 5. Update Version Upgrade Guide + +Add entries to `${UPGRADE_GUIDE}` following existing entries in that file and guidance in `docs/content/breaking-changes/make-a-breaking-change.md`: +- **Resource Removal**: Under `## Resources`, add `## Resource: google_ is now removed` with migration advice. +- **Data Source Removal**: Under `## Datasources`, add `## Datasource: google_ is now removed`. +- **Field Removal**: Under `## Resource: google_`, add `### is now removed` explaining the removal and replacement argument. + +--- + +### 6. Pre-Gen Checks & Code Generation + +1. Run fast static pre-gen checks: [run-pre-gen-checks](.agents/skills/utils/run-pre-gen-checks/SKILL.md). +2. Generate provider code: [generate-provider](.agents/skills/operations/generate-provider/SKILL.md). +3. Verify compilation: Run `make build` in downstream provider repositories. + +--- + +### 7. Verification Testing + +Invoke [qa-test-runner](.agents/skills/operations/qa-test-runner/SKILL.md) to run acceptance tests for remaining or adjacent resources to ensure no regressions. + +--- + +### 8. PR Creation & Release Note + +Execute [create-pr](.agents/skills/operations/create-pr/SKILL.md) targeting `${FEATURE_BRANCH}`: +- **Title Length Limit**: Must be strictly **under 70 characters** (e.g. `: remove deprecated google__* for ${MAJOR_VERSION}`). +- **Body**: + ```markdown + ```release-note:breaking-change + : removed deprecated `` from `google_` + ``` + *(or `: removed deprecated `google_` resource/data source`)* + ``` +- **Pre-Filled Hyperlink**: Always generate and provide a pre-filled markdown compare URL in chat for easy user submission. diff --git a/docs/content/document/add-documentation.md b/docs/content/document/add-documentation.md index 15c5fce8c0c7..71a8c8bb4509 100644 --- a/docs/content/document/add-documentation.md +++ b/docs/content/document/add-documentation.md @@ -7,7 +7,7 @@ aliases: # Add documentation -Documentation is autogenerated based on the [resource]({{< ref "/develop/add-resource" >}}) and [field({{< ref "/develop/add-fields" >}}] configurations. This page describes how to add documentation to resources and fields. +Documentation is autogenerated based on the [resource]({{< ref "/develop/add-resource" >}}) and [field]({{< ref "/develop/add-fields" >}}) configurations. This page describes how to add documentation to resources and fields. For more information about types of resources and the generation process overall, see [How Magic Modules works]({{< ref "/" >}}). diff --git a/mmv1/products/vertexaiaad/AgentAnomalyDetectionScope.yaml b/mmv1/products/vertexaiaad/AgentAnomalyDetectionScope.yaml new file mode 100644 index 000000000000..5b34b83d76c6 --- /dev/null +++ b/mmv1/products/vertexaiaad/AgentAnomalyDetectionScope.yaml @@ -0,0 +1,117 @@ +# Copyright 2026 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +name: 'AgentAnomalyDetectionScope' +min_version: 'beta' +exclude_tgc: true +description: |- + An Agent Anomaly Detection (AAD) scope defines the Cloud Logging and + Observability buckets that AAD monitors for anomalous agent behavior within + a location. Creating a scope provisions a dedicated tenant project for + detection. Only one scope may exist per location. +references: + guides: + 'Agent Anomaly Detection': 'https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/' + api: 'https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/projects.locations.agentAnomalyDetectionScopes' +base_url: 'projects/{{project}}/locations/{{region}}/agentAnomalyDetectionScopes' +self_link: 'projects/{{project}}/locations/{{region}}/agentAnomalyDetectionScopes/{{agent_anomaly_detection_scope_id}}' +id_format: 'projects/{{project}}/locations/{{region}}/agentAnomalyDetectionScopes/{{agent_anomaly_detection_scope_id}}' +import_format: + - 'projects/{{project}}/locations/{{region}}/agentAnomalyDetectionScopes/{{agent_anomaly_detection_scope_id}}' +create_url: 'projects/{{project}}/locations/{{region}}/agentAnomalyDetectionScopes?agentAnomalyDetectionScopeId={{agent_anomaly_detection_scope_id}}' +# The scope has no public update method, so all configurable fields are +# immutable; changing them requires recreating the scope. +immutable: true +timeouts: + insert_minutes: 40 + delete_minutes: 20 +sweeper: + url_substitutions: + - region: us +async: + actions: ['create', 'delete'] + type: 'PollAsync' + check_response_func_existence: 'PollCheckForScopeActive' + check_response_func_absence: 'PollCheckForScopeDeleted' + suppress_error: false + target_occurrences: 1 +samples: + - name: 'vertex_ai_agent_anomaly_detection_scope_basic' + primary_resource_id: 'scope' + min_version: 'beta' + bootstrap_iam: + - member: "serviceAccount:service-{project_number}@gcp-sa-aiplatform.iam.gserviceaccount.com" + role: "roles/logging.configWriter" + steps: + - name: 'vertex_ai_agent_anomaly_detection_scope_basic' + resource_id_vars: + scope_id: 'agent-anomaly-detection-scope' + log_bucket_id: 'aad-log-bucket' +parameters: + - name: 'region' + type: String + description: The region of the AgentAnomalyDetectionScope, e.g. us-central1. + url_param_only: true + required: true + immutable: true + - name: 'agentAnomalyDetectionScopeId' + type: String + description: |- + The ID to use for the AgentAnomalyDetectionScope, which will become the + final component of the scope's resource name. This value should be 1-63 + characters and valid characters are /[a-z][0-9]-/. + url_param_only: true + required: true + immutable: true +properties: + - name: 'name' + type: String + description: |- + The resource name of the AgentAnomalyDetectionScope, in the format + projects/{{project}}/locations/{{location}}/agentAnomalyDetectionScopes/{{agent_anomaly_detection_scope}} + output: true + - name: 'displayName' + type: String + description: User provided display name of the AgentAnomalyDetectionScope. + immutable: true + - name: 'logBuckets' + type: Array + item_type: + type: String + description: |- + Customer owned Cloud Logging bucket resource names attached to this scope. + Format: projects/{{project}}/locations/{{location}}/buckets/{{bucket}}. + required: true + immutable: true + - name: 'observabilityBuckets' + type: Array + item_type: + type: String + description: |- + Customer owned Cloud Observability bucket resource names attached to this + scope. + Format: projects/{{project}}/locations/{{location}}/buckets/{{bucket}}/datasets/{{dataset}}. + required: true + immutable: true + - name: 'state' + type: Enum + description: |- + The lifecycle state of the scope. + output: true + enum_values: + - 'CREATING' + - 'ACTIVE' + - 'DELETING' + - 'FAILED' + - 'UPDATING' diff --git a/mmv1/products/vertexaiaad/product.yaml b/mmv1/products/vertexaiaad/product.yaml new file mode 100644 index 000000000000..166d4bed3415 --- /dev/null +++ b/mmv1/products/vertexaiaad/product.yaml @@ -0,0 +1,22 @@ +# Copyright 2026 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +name: VertexAiAad +legacy_name: vertex_ai +display_name: Vertex AI +scopes: + - https://www.googleapis.com/auth/cloud-platform +versions: + - name: beta + base_url: https://aiplatform.{{region}}.rep.googleapis.com/v1beta1/ diff --git a/mmv1/templates/terraform/samples/services/vertexaiaad/vertex_ai_agent_anomaly_detection_scope_basic.tf.tmpl b/mmv1/templates/terraform/samples/services/vertexaiaad/vertex_ai_agent_anomaly_detection_scope_basic.tf.tmpl new file mode 100644 index 000000000000..cc94224ed7f4 --- /dev/null +++ b/mmv1/templates/terraform/samples/services/vertexaiaad/vertex_ai_agent_anomaly_detection_scope_basic.tf.tmpl @@ -0,0 +1,25 @@ +resource "google_logging_project_bucket_config" "{{index $.ResourceIdVars "log_bucket_id"}}" { + provider = google-beta + project = data.google_project.project.project_id + location = "us" + retention_days = 30 + bucket_id = "{{index $.ResourceIdVars "log_bucket_id"}}" + enable_analytics = true +} + +resource "google_vertex_ai_agent_anomaly_detection_scope" "{{$.PrimaryResourceId}}" { + provider = google-beta + region = "us" + agent_anomaly_detection_scope_id = "{{index $.ResourceIdVars "scope_id"}}" + display_name = "Basic AAD scope" + log_buckets = [ + google_logging_project_bucket_config.{{index $.ResourceIdVars "log_bucket_id"}}.id, + ] + observability_buckets = [ + "projects/${data.google_project.project.project_id}/locations/us/buckets/_Trace/datasets/Spans", + ] +} + +data "google_project" "project" { + provider = google-beta +} diff --git a/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt b/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt index 97d36d903439..cc97353b4617 100644 --- a/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt +++ b/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt @@ -931,6 +931,11 @@ var ServicesListBeta = mapOf( "displayName" to "Vertexai", "path" to "./google-beta/services/vertexai" ), + "vertexaiaad" to mapOf( + "name" to "vertexaiaad", + "displayName" to "Vertexaiaad", + "path" to "./google-beta/services/vertexaiaad" + ), "vmwareengine" to mapOf( "name" to "vmwareengine", "displayName" to "Vmwareengine", diff --git a/mmv1/third_party/terraform/go.mod b/mmv1/third_party/terraform/go.mod index b586f8f270f6..62c2944a08ec 100644 --- a/mmv1/third_party/terraform/go.mod +++ b/mmv1/third_party/terraform/go.mod @@ -36,7 +36,7 @@ require ( github.com/stretchr/testify v1.11.1 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 - golang.org/x/net v0.57.0 + golang.org/x/net v0.58.0 golang.org/x/oauth2 v0.36.0 google.golang.org/api v0.293.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20260807164820-c8921c73eeea @@ -119,13 +119,13 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect golang.org/x/crypto v0.55.0 // indirect - golang.org/x/mod v0.38.0 // indirect + golang.org/x/mod v0.40.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959 // indirect golang.org/x/text v0.41.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.48.0 // indirect + golang.org/x/tools v0.49.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect diff --git a/mmv1/third_party/terraform/go.sum b/mmv1/third_party/terraform/go.sum index ca5855e3db2f..6c1dd4d6b565 100644 --- a/mmv1/third_party/terraform/go.sum +++ b/mmv1/third_party/terraform/go.sum @@ -352,8 +352,8 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= -golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= +golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= +golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -364,8 +364,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= -golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= @@ -397,8 +397,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959 h1:RJhm5l6Fo4rmEIcndxDllNhhf/fAx8qIm4t6A7vpm2A= -golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959/go.mod h1:LV7u5Oco+Z/g6XI7PqN+EUUUGGkEcmB1uj2ceI0fOVg= +golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5 h1:ZUSxONxc981v7AW7QUg+I9WwZzSTTJ019ENBYr5pV/Q= +golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5/go.mod h1:LVehoXe41cL5SCVQilsV7Gg6BNG+Js6P9PhSbYTIUkQ= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= @@ -421,8 +421,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= -golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/mmv1/third_party/terraform/services/backupdr/data_source_backup_dr_backup_plan_association_test.go b/mmv1/third_party/terraform/services/backupdr/data_source_backup_dr_backup_plan_association_test.go index 19d06a1d9bee..00e8ab9bd985 100644 --- a/mmv1/third_party/terraform/services/backupdr/data_source_backup_dr_backup_plan_association_test.go +++ b/mmv1/third_party/terraform/services/backupdr/data_source_backup_dr_backup_plan_association_test.go @@ -339,8 +339,9 @@ resource "google_service_account" "default" { } resource "google_sql_database_instance" "instance" { - name = "default-%{random_suffix}" + name = "tf-test-instance-%{random_suffix}" database_version = "MYSQL_8_0" + root_password = "tf-test-password-%{random_suffix}" region = "us-central1" deletion_protection = false settings { diff --git a/mmv1/third_party/terraform/services/backupdr/data_source_backup_dr_data_source_reference_test.go b/mmv1/third_party/terraform/services/backupdr/data_source_backup_dr_data_source_reference_test.go index 4dc8a61746a8..c9a93d4f2cc0 100644 --- a/mmv1/third_party/terraform/services/backupdr/data_source_backup_dr_data_source_reference_test.go +++ b/mmv1/third_party/terraform/services/backupdr/data_source_backup_dr_data_source_reference_test.go @@ -71,8 +71,9 @@ data "google_project" "project" {} resource "google_sql_database_instance" "instance" { - name = "default-%{random_suffix}" + name = "tf-test-instance-%{random_suffix}" database_version = "MYSQL_8_0" + root_password = "tf-test-password-%{random_suffix}" region = "us-central1" deletion_protection = false settings { @@ -187,8 +188,9 @@ resource "google_service_account" "default" { } resource "google_sql_database_instance" "instance" { - name = "default-%{random_suffix}" + name = "tf-test-instance-%{random_suffix}" database_version = "MYSQL_8_0" + root_password = "tf-test-password-%{random_suffix}" region = "us-central1" deletion_protection = false settings { @@ -312,8 +314,9 @@ resource "google_service_account" "default" { } resource "google_sql_database_instance" "instance" { - name = "default-%{random_suffix}" + name = "tf-test-instance-%{random_suffix}" database_version = "MYSQL_8_0" + root_password = "tf-test-password-%{random_suffix}" region = "us-central1" deletion_protection = false settings { diff --git a/mmv1/third_party/terraform/services/compute/compute_instance_helpers.go.tmpl b/mmv1/third_party/terraform/services/compute/compute_instance_helpers.go.tmpl index feb7b5704a90..6a40daff4e1a 100644 --- a/mmv1/third_party/terraform/services/compute/compute_instance_helpers.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/compute_instance_helpers.go.tmpl @@ -1058,75 +1058,6 @@ func resourceInstanceTags(d tpgresource.TerraformResourceData) map[string]interf } } -func resourceInstanceTagsOmitEmpty(d tpgresource.TerraformResourceData) map[string]interface{} { - tags := map[string]interface{}{} - v := d.Get("tags") - if v == nil { - return tags - } - vs := v.(*schema.Set) - if vs.Len() > 0 { - items := make([]string, vs.Len()) - for i, v := range vs.List() { - items[i] = v.(string) - } - tags["items"] = items - } - if fingerprint := d.Get("tags_fingerprint").(string); fingerprint != "" { - tags["fingerprint"] = fingerprint - } - return tags -} - -func schedulingOmitEmpty(scheduling map[string]interface{}) map[string]interface{} { - if scheduling == nil { - return nil - } - result := map[string]interface{}{} - for k, v := range scheduling { - if k == "automaticRestart" { - result[k] = v - continue - } - if v == nil { - continue - } - switch val := v.(type) { - case bool: - if val { - result[k] = v - } - case string: - if val != "" { - result[k] = v - } - case int: - if val != 0 { - result[k] = v - } - case int64: - if val != 0 { - result[k] = v - } - case float64: - if val != 0 { - result[k] = v - } - case map[string]interface{}: - if val != nil { - result[k] = v - } - case []interface{}: - if len(val) > 0 { - result[k] = v - } - default: - result[k] = v - } - } - return result -} - func expandShieldedVmConfigs(d tpgresource.TerraformResourceData) map[string]interface{} { if _, ok := d.GetOk("shielded_instance_config"); !ok { return nil @@ -1583,23 +1514,13 @@ func expandComputeInstanceSourceEncryptionKey(d tpgresource.TerraformResourceDat } cekRes := cek.([]interface{})[0].(map[string]interface{}) - result := map[string]interface{}{} - if v, _ := cekRes["rsa_encrypted_key"].(string); v != "" { - result["rsaEncryptedKey"] = v - } - if v, _ := cekRes["raw_key"].(string); v != "" { - result["rawKey"] = v - } - if v, _ := cekRes["kms_key_self_link"].(string); v != "" { - result["kmsKeyName"] = v - } - if v, _ := cekRes["sha256"].(string); v != "" { - result["sha256"] = v - } - if v, _ := cekRes["kms_key_service_account"].(string); v != "" { - result["kmsKeyServiceAccount"] = v + return map[string]interface{}{ + "rsaEncryptedKey": cekRes["rsa_encrypted_key"].(string), + "rawKey": cekRes["raw_key"].(string), + "kmsKeyName": cekRes["kms_key_self_link"].(string), + "sha256": cekRes["sha256"].(string), + "kmsKeyServiceAccount": cekRes["kms_key_service_account"].(string), } - return result } func flattenComputeInstanceSourceEncryptionKey(v map[string]interface{}) []map[string]interface{} { diff --git a/mmv1/third_party/terraform/services/compute/data_source_compute_network_endpoint_groups.go b/mmv1/third_party/terraform/services/compute/data_source_compute_network_endpoint_groups.go index c81fb5edec71..c50c18a8a361 100644 --- a/mmv1/third_party/terraform/services/compute/data_source_compute_network_endpoint_groups.go +++ b/mmv1/third_party/terraform/services/compute/data_source_compute_network_endpoint_groups.go @@ -2,6 +2,7 @@ package compute import ( "fmt" + neturl "net/url" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-google/google/registry" @@ -65,30 +66,73 @@ func dataSourceComputeNetworkEndpointGroupsRead(d *schema.ResourceData, meta int networkEndpointGroups := make([]map[string]interface{}, 0) - networkEndpointGroupsList, err := NewClient(config, userAgent).NetworkEndpointGroups.List(project, zone).Filter(filter).Do() - if err != nil { - return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("NetworkEndpointGroups : %s %s", project, zone)) - } + baseURL := fmt.Sprintf("%sprojects/%s/zones/%s/networkEndpointGroups", transport_tpg.BaseUrl(Product, config), project, zone) + pageToken := "" - for _, neg := range networkEndpointGroupsList.Items { - network, err := tpgresource.GetRelativePath(neg.Network) - if err != nil { - return err + for { + params := neturl.Values{} + if filter != "" { + params.Set("filter", filter) } - subnetwork, err := tpgresource.GetRelativePath(neg.Subnetwork) - if err != nil { - return err + if pageToken != "" { + params.Set("pageToken", pageToken) + } + url := baseURL + if len(params) > 0 { + url = fmt.Sprintf("%s?%s", url, params.Encode()) } - networkEndpointGroups = append(networkEndpointGroups, map[string]interface{}{ - "self_link": neg.SelfLink, - "name": neg.Name, - "description": neg.Description, - "network_endpoint_type": neg.NetworkEndpointType, - "network": network, - "subnetwork": subnetwork, - "default_port": neg.DefaultPort, - "size": neg.Size, + networkEndpointGroupsList, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: project, + RawURL: url, + UserAgent: userAgent, }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("NetworkEndpointGroups : %s %s", project, zone)) + } + + if rawItems, ok := networkEndpointGroupsList["items"].([]interface{}); ok { + for _, raw := range rawItems { + neg, ok := raw.(map[string]interface{}) + if !ok { + continue + } + negNetwork, _ := neg["network"].(string) + network, err := tpgresource.GetRelativePath(negNetwork) + if err != nil { + return err + } + negSubnetwork, _ := neg["subnetwork"].(string) + subnetwork, err := tpgresource.GetRelativePath(negSubnetwork) + if err != nil { + return err + } + defaultPort := 0 + if v, ok := neg["defaultPort"].(float64); ok { + defaultPort = int(v) + } + size := 0 + if v, ok := neg["size"].(float64); ok { + size = int(v) + } + networkEndpointGroups = append(networkEndpointGroups, map[string]interface{}{ + "self_link": neg["selfLink"], + "name": neg["name"], + "description": neg["description"], + "network_endpoint_type": neg["networkEndpointType"], + "network": network, + "subnetwork": subnetwork, + "default_port": defaultPort, + "size": size, + }) + } + } + + pageToken, _ = networkEndpointGroupsList["nextPageToken"].(string) + if pageToken == "" { + break + } } if err := d.Set("network_endpoint_groups", networkEndpointGroups); err != nil { diff --git a/mmv1/third_party/terraform/services/compute/data_source_google_compute_instance.go b/mmv1/third_party/terraform/services/compute/data_source_google_compute_instance.go index 084762d57f09..edfa76a7f3bd 100644 --- a/mmv1/third_party/terraform/services/compute/data_source_google_compute_instance.go +++ b/mmv1/third_party/terraform/services/compute/data_source_google_compute_instance.go @@ -36,42 +36,38 @@ func dataSourceGoogleComputeInstanceRead(d *schema.ResourceData, meta interface{ id := fmt.Sprintf("projects/%s/zones/%s/instances/%s", project, zone, name) - baseURL, err := tpgresource.ReplaceVars(d, config, "{{ComputeBasePath}}") - if err != nil { - return err - } - url := fmt.Sprintf("%sprojects/%s/zones/%s/instances/%s", baseURL, project, zone, name) - instance, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "GET", - Project: project, - RawURL: url, - UserAgent: userAgent, - }) + instance, err := NewClient(config, userAgent).Instances.Get(project, zone, name).Do() if err != nil { return transport_tpg.HandleDataSourceNotFoundError(err, d, fmt.Sprintf("Instance %s", name), id) } - metadataMap, _ := instance["metadata"].(map[string]interface{}) + var metadataMap map[string]interface{} + if instance.Metadata != nil { + if metadataMap, err = tpgresource.ConvertToMap(instance.Metadata); err != nil { + return fmt.Errorf("error converting metadata: %s", err) + } + } md := flattenMetadataBeta(metadataMap) if err = d.Set("metadata", md); err != nil { return fmt.Errorf("error setting metadata: %s", err) } - if err := d.Set("can_ip_forward", instance["canIpForward"]); err != nil { + if err := d.Set("can_ip_forward", instance.CanIpForward); err != nil { return fmt.Errorf("Error setting can_ip_forward: %s", err) } - machineType, _ := instance["machineType"].(string) - if err := d.Set("machine_type", tpgresource.GetResourceNameFromSelfLink(machineType)); err != nil { + if err := d.Set("machine_type", tpgresource.GetResourceNameFromSelfLink(instance.MachineType)); err != nil { return fmt.Errorf("Error setting machine_type: %s", err) } - if err := d.Set("hostname", instance["hostname"]); err != nil { + if err := d.Set("hostname", instance.Hostname); err != nil { return fmt.Errorf("Error setting hostname: %s", err) } // Set the networks // Use the first external IP found for the default connection info. - networkInterfacesRaw, _ := instance["networkInterfaces"].([]interface{}) + networkInterfacesRaw, err := networkInterfacesToInterface(instance.NetworkInterfaces) + if err != nil { + return err + } networkInterfaces, _, internalIP, externalIP, err := flattenNetworkInterfaces(d, config, networkInterfacesRaw) if err != nil { return err @@ -95,63 +91,55 @@ func dataSourceGoogleComputeInstanceRead(d *schema.ResourceData, meta interface{ }) // Set the metadata fingerprint if there is one. - if metadataMap != nil { - if err := d.Set("metadata_fingerprint", metadataMap["fingerprint"]); err != nil { + if instance.Metadata != nil { + if err := d.Set("metadata_fingerprint", instance.Metadata.Fingerprint); err != nil { return fmt.Errorf("Error setting metadata_fingerprint: %s", err) } } // Set the tags fingerprint if there is one. - if tags, ok := instance["tags"].(map[string]interface{}); ok { - if err := d.Set("tags_fingerprint", tags["fingerprint"]); err != nil { + if instance.Tags != nil { + if err := d.Set("tags_fingerprint", instance.Tags.Fingerprint); err != nil { return fmt.Errorf("Error setting tags_fingerprint: %s", err) } - items, _ := tags["items"].([]interface{}) - if err := d.Set("tags", items); err != nil { + if err := d.Set("tags", tpgresource.ConvertStringArrToInterface(instance.Tags.Items)); err != nil { return fmt.Errorf("Error setting tags: %s", err) } } - instanceLabels, _ := instance["labels"].(map[string]interface{}) - if err := d.Set("labels", instanceLabels); err != nil { + if err := d.Set("labels", instance.Labels); err != nil { return err } - if err := d.Set("terraform_labels", instanceLabels); err != nil { + if err := d.Set("terraform_labels", instance.Labels); err != nil { return err } - if labelFingerprint, _ := instance["labelFingerprint"].(string); labelFingerprint != "" { - if err := d.Set("label_fingerprint", labelFingerprint); err != nil { + if instance.LabelFingerprint != "" { + if err := d.Set("label_fingerprint", instance.LabelFingerprint); err != nil { return fmt.Errorf("Error setting label_fingerprint: %s", err) } } attachedDisks := []map[string]interface{}{} scratchDisks := []map[string]interface{}{} - instanceDisks, _ := instance["disks"].([]interface{}) - for _, rawDisk := range instanceDisks { - disk, ok := rawDisk.(map[string]interface{}) - if !ok { - continue - } - if isBoot, _ := disk["boot"].(bool); isBoot { + for _, disk := range instance.Disks { + if disk.Boot { err = d.Set("boot_disk", flattenBootDisk(d, disk, config)) if err != nil { return err } - } else if diskType, _ := disk["type"].(string); diskType == "SCRATCH" { + } else if disk.Type == "SCRATCH" { scratchDisks = append(scratchDisks, flattenScratchDisk(disk)) } else { - diskSource, _ := disk["source"].(string) di := map[string]interface{}{ - "source": tpgresource.ConvertSelfLinkToV1(diskSource), - "device_name": disk["deviceName"], - "mode": disk["mode"], + "source": tpgresource.ConvertSelfLinkToV1(disk.Source), + "device_name": disk.DeviceName, + "mode": disk.Mode, } - if key, ok := disk["diskEncryptionKey"].(map[string]interface{}); ok { - di["disk_encryption_key_sha256"] = key["sha256"] - di["kms_key_self_link"] = key["kmsKeyName"] + if key := disk.DiskEncryptionKey; key != nil { + di["disk_encryption_key_sha256"] = key.Sha256 + di["kms_key_self_link"] = key.KmsKeyName } attachedDisks = append(attachedDisks, di) } @@ -165,20 +153,21 @@ func dataSourceGoogleComputeInstanceRead(d *schema.ResourceData, meta interface{ } } - instanceServiceAccounts, _ := instance["serviceAccounts"].([]interface{}) - err = d.Set("service_account", flattenServiceAccounts(instanceServiceAccounts)) + err = d.Set("service_account", flattenServiceAccounts(serviceAccountsToInterface(instance.ServiceAccounts))) if err != nil { return err } - schedulingMap, _ := instance["scheduling"].(map[string]interface{}) + schedulingMap, err := tpgresource.ConvertToMap(instance.Scheduling) + if err != nil { + return fmt.Errorf("Error converting scheduling: %s", err) + } err = d.Set("scheduling", flattenScheduling(schedulingMap)) if err != nil { return err } - instanceGuestAccelerators, _ := instance["guestAccelerators"].([]interface{}) - err = d.Set("guest_accelerator", flattenGuestAccelerators(instanceGuestAccelerators)) + err = d.Set("guest_accelerator", flattenGuestAccelerators(guestAcceleratorsToInterface(instance.GuestAccelerators))) if err != nil { return err } @@ -188,13 +177,23 @@ func dataSourceGoogleComputeInstanceRead(d *schema.ResourceData, meta interface{ return err } - shieldedVmConfigMap, _ := instance["shieldedInstanceConfig"].(map[string]interface{}) + var shieldedVmConfigMap map[string]interface{} + if sic := instance.ShieldedInstanceConfig; sic != nil { + shieldedVmConfigMap = map[string]interface{}{ + "enableSecureBoot": sic.EnableSecureBoot, + "enableVtpm": sic.EnableVtpm, + "enableIntegrityMonitoring": sic.EnableIntegrityMonitoring, + } + } err = d.Set("shielded_instance_config", flattenShieldedVmConfig(shieldedVmConfigMap)) if err != nil { return err } - displayDeviceMap, _ := instance["displayDevice"].(map[string]interface{}) + var displayDeviceMap map[string]interface{} + if instance.DisplayDevice != nil { + displayDeviceMap = map[string]interface{}{"enableDisplay": instance.DisplayDevice.EnableDisplay} + } err = d.Set("enable_display", flattenEnableDisplay(displayDeviceMap)) if err != nil { return err @@ -203,44 +202,41 @@ func dataSourceGoogleComputeInstanceRead(d *schema.ResourceData, meta interface{ if err := d.Set("attached_disk", ads); err != nil { return fmt.Errorf("Error setting attached_disk: %s", err) } - if err := d.Set("cpu_platform", instance["cpuPlatform"]); err != nil { + if err := d.Set("cpu_platform", instance.CpuPlatform); err != nil { return fmt.Errorf("Error setting cpu_platform: %s", err) } - if err := d.Set("min_cpu_platform", instance["minCpuPlatform"]); err != nil { + if err := d.Set("min_cpu_platform", instance.MinCpuPlatform); err != nil { return fmt.Errorf("Error setting min_cpu_platform: %s", err) } - if err := d.Set("deletion_protection", instance["deletionProtection"]); err != nil { + if err := d.Set("deletion_protection", instance.DeletionProtection); err != nil { return fmt.Errorf("Error setting deletion_protection: %s", err) } - selfLink, _ := instance["selfLink"].(string) - if err := d.Set("self_link", tpgresource.ConvertSelfLinkToV1(selfLink)); err != nil { + if err := d.Set("self_link", tpgresource.ConvertSelfLinkToV1(instance.SelfLink)); err != nil { return fmt.Errorf("Error setting self_link: %s", err) } - if err := d.Set("instance_id", fmt.Sprintf("%d", getInt(instance["id"]))); err != nil { + if err := d.Set("instance_id", fmt.Sprintf("%d", instance.Id)); err != nil { return fmt.Errorf("Error setting instance_id: %s", err) } if err := d.Set("project", project); err != nil { return fmt.Errorf("Error setting project: %s", err) } - instanceZone, _ := instance["zone"].(string) - if err := d.Set("zone", tpgresource.GetResourceNameFromSelfLink(instanceZone)); err != nil { + if err := d.Set("zone", tpgresource.GetResourceNameFromSelfLink(instance.Zone)); err != nil { return fmt.Errorf("Error setting zone: %s", err) } - if err := d.Set("current_status", instance["status"]); err != nil { + if err := d.Set("current_status", instance.Status); err != nil { return fmt.Errorf("Error setting current_status: %s", err) } - instanceName, _ := instance["name"].(string) - if err := d.Set("name", instanceName); err != nil { + if err := d.Set("name", instance.Name); err != nil { return fmt.Errorf("Error setting name: %s", err) } - if err := d.Set("key_revocation_action_type", instance["keyRevocationActionType"]); err != nil { + if err := d.Set("key_revocation_action_type", instance.KeyRevocationActionType); err != nil { return fmt.Errorf("Error setting key_revocation_action_type: %s", err) } - if err := d.Set("creation_timestamp", instance["creationTimestamp"]); err != nil { + if err := d.Set("creation_timestamp", instance.CreationTimestamp); err != nil { return fmt.Errorf("Error setting creation_timestamp: %s", err) } - d.SetId(fmt.Sprintf("projects/%s/zones/%s/instances/%s", project, tpgresource.GetResourceNameFromSelfLink(instanceZone), instanceName)) + d.SetId(fmt.Sprintf("projects/%s/zones/%s/instances/%s", project, tpgresource.GetResourceNameFromSelfLink(instance.Zone), instance.Name)) return nil } diff --git a/mmv1/third_party/terraform/services/compute/list_google_compute_instance.go.tmpl b/mmv1/third_party/terraform/services/compute/list_google_compute_instance.go.tmpl index 930f2804d8bb..602ba6b94897 100644 --- a/mmv1/third_party/terraform/services/compute/list_google_compute_instance.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/list_google_compute_instance.go.tmpl @@ -10,6 +10,11 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +{{ if eq $.TargetVersionName `ga` }} + "google.golang.org/api/compute/v1" +{{- else }} + compute "google.golang.org/api/compute/v0.beta" +{{- end }} "github.com/hashicorp/terraform-provider-google/google/registry" "github.com/hashicorp/terraform-provider-google/google/tpgresource" @@ -86,16 +91,18 @@ func (listR *GoogleComputeInstanceListResource) List(ctx context.Context, listRe } func flattenComputeInstanceListItem(res map[string]interface{}, d *schema.ResourceData, config *transport_tpg.Config, project string) error { - instanceName, _ := res["name"].(string) - if instanceName == "" { + var instance compute.Instance + if err := tpgresource.Convert(res, &instance); err != nil { + return fmt.Errorf("error converting compute instance list response: %w", err) + } + if instance.Name == "" { return fmt.Errorf("missing name in compute instance list response") } - instanceZone, _ := res["zone"].(string) - zone := tpgresource.GetResourceNameFromSelfLink(instanceZone) + zone := tpgresource.GetResourceNameFromSelfLink(instance.Zone) - d.SetId(fmt.Sprintf("projects/%s/zones/%s/instances/%s", project, zone, instanceName)) - return populateComputeInstanceResourceData(d, res, project, zone, config) + d.SetId(fmt.Sprintf("projects/%s/zones/%s/instances/%s", project, zone, instance.Name)) + return populateComputeInstanceResourceData(d, &instance, project, zone, config) } func ListComputeInstances(config *transport_tpg.Config, project, zone string, callback func(rd *schema.ResourceData) error) error { diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_instance.go.tmpl index 96ad7ce7e77d..5f1c2f90d055 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance.go.tmpl @@ -22,6 +22,12 @@ import ( "github.com/hashicorp/terraform-provider-google/google/tpgresource" transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" "github.com/hashicorp/terraform-provider-google/google/verify" + +{{ if eq $.TargetVersionName `ga` }} + "google.golang.org/api/compute/v1" +{{- else }} + compute "google.golang.org/api/compute/v0.beta" +{{- end }} ) func IpCidrRangeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { @@ -1783,7 +1789,7 @@ be from 0 to 999,999,999 inclusive.`, } } -func getInstance(config *transport_tpg.Config, d *schema.ResourceData) (map[string]interface{}, error) { +func getInstance(config *transport_tpg.Config, d *schema.ResourceData) (*compute.Instance, error) { project, err := tpgresource.GetProject(d, config) if err != nil { return nil, err @@ -1812,10 +1818,14 @@ func getInstance(config *transport_tpg.Config, d *schema.ResourceData) (map[stri if err != nil { return nil, transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("Instance %s", d.Get("name").(string))) } - return res, nil + var instance compute.Instance + if err = tpgresource.Convert(res, &instance); err != nil { + return nil, fmt.Errorf("Error parsing instance response: %s", err) + } + return &instance, nil } -func getDisk(diskUri string, d *schema.ResourceData, config *transport_tpg.Config) (map[string]interface{}, error) { +func getDisk(diskUri string, d *schema.ResourceData, config *transport_tpg.Config) (*compute.Disk, error) { userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) if err != nil { return nil, err @@ -1848,10 +1858,14 @@ func getDisk(diskUri string, d *schema.ResourceData, config *transport_tpg.Confi return nil, err } - return res, nil + var disk compute.Disk + if err = tpgresource.Convert(res, &disk); err != nil { + return nil, fmt.Errorf("Error parsing disk response: %s", err) + } + return &disk, nil } -func expandComputeInstance(project string, d *schema.ResourceData, config *transport_tpg.Config) (map[string]interface{}, error) { +func expandComputeInstance(project string, d *schema.ResourceData, config *transport_tpg.Config) (*compute.Instance, error) { // Get the machine type var machineTypeUrl string if mt, ok := d.GetOk("machine_type"); ok { @@ -1865,9 +1879,10 @@ func expandComputeInstance(project string, d *schema.ResourceData, config *trans } // Build up the list of disks - disks := []interface{}{} + + disks := []*compute.AttachedDisk{} if _, hasBootDisk := d.GetOk("boot_disk"); hasBootDisk { - bootDisk, err := expandBootDisk(d, config, project) + bootDisk, err := expandBootDiskTyped(d, config, project) if err != nil { return nil, err } @@ -1875,7 +1890,7 @@ func expandComputeInstance(project string, d *schema.ResourceData, config *trans } if _, hasScratchDisk := d.GetOk("scratch_disk"); hasScratchDisk { - scratchDisks, err := expandScratchDisks(d, config, project) + scratchDisks, err := expandScratchDisksTyped(d, config, project) if err != nil { return nil, err } @@ -1886,7 +1901,7 @@ func expandComputeInstance(project string, d *schema.ResourceData, config *trans for i := 0; i < attachedDisksCount; i++ { diskConfig := d.Get(fmt.Sprintf("attached_disk.%d", i)).(map[string]interface{}) - disk, err := expandAttachedDisk(diskConfig, d, config) + disk, err := expandAttachedDiskTyped(diskConfig, d, config) if err != nil { return nil, err } @@ -1894,7 +1909,9 @@ func expandComputeInstance(project string, d *schema.ResourceData, config *trans disks = append(disks, disk) } - networkInterfaces, err := expandNetworkInterfaces(d, config) + + + networkInterfaces, err := expandNetworkInterfacesTyped(d, config) if err != nil { return nil, fmt.Errorf("Error creating network interfaces: %s", err) } @@ -1902,89 +1919,99 @@ func expandComputeInstance(project string, d *schema.ResourceData, config *trans if err != nil { return nil, fmt.Errorf("Error creating network performance config: %s", err) } - accels, err := expandInstanceGuestAccelerators(d, config) - if err != nil { - return nil, fmt.Errorf("Error creating guest accelerators: %s", err) + var networkPerformanceConfig *compute.NetworkPerformanceConfig + if npcMap != nil { + networkPerformanceConfig = &compute.NetworkPerformanceConfig{} + if err := tpgresource.Convert(npcMap, networkPerformanceConfig); err != nil { + return nil, fmt.Errorf("Error converting networkPerformanceConfig: %s", err) + } } - reservationAffinity, err := expandReservationAffinity(d) + accels, err := expandInstanceGuestAcceleratorsTyped(d, config) if err != nil { - return nil, fmt.Errorf("Error creating reservation affinity: %s", err) + return nil, fmt.Errorf("Error creating guest accelerators: %s", err) } - // Create the instance information - instance := map[string]interface{}{ - "canIpForward": d.Get("can_ip_forward").(bool), - "deletionProtection": d.Get("deletion_protection").(bool), - "name": d.Get("name").(string), - } - if v := d.Get("description").(string); v != "" { - instance["description"] = v - } - if len(disks) > 0 { - instance["disks"] = disks - } - if machineTypeUrl != "" { - instance["machineType"] = machineTypeUrl - } - if len(networkInterfaces) > 0 { - instance["networkInterfaces"] = networkInterfaces - } - if npcMap != nil { - instance["networkPerformanceConfig"] = npcMap - } - if tagsMap := resourceInstanceTagsOmitEmpty(d); tagsMap != nil { - instance["tags"] = tagsMap - } - if labels := tpgresource.ExpandEffectiveLabels(d); len(labels) > 0 { - instance["labels"] = labels - } - if sa := expandServiceAccounts(d.Get("service_account").([]interface{})); len(sa) > 0 { - instance["serviceAccounts"] = sa - } - if len(accels) > 0 { - instance["guestAccelerators"] = accels - } - if v := d.Get("min_cpu_platform").(string); v != "" { - instance["minCpuPlatform"] = v - } - if v := d.Get("hostname").(string); v != "" { - instance["hostname"] = v - } - if rp := tpgresource.ConvertStringArr(d.Get("resource_policies").([]interface{})); len(rp) > 0 { - instance["resourcePolicies"] = rp + tagsMap := resourceInstanceTags(d) + var tags *compute.Tags + if tagsMap != nil { + tags = &compute.Tags{} + if err := tpgresource.Convert(tagsMap, tags); err != nil { + return nil, fmt.Errorf("Error converting tags: %s", err) + } } - if reservationAffinity != nil { - instance["reservationAffinity"] = reservationAffinity + + reservationAffinityMap, err := expandReservationAffinity(d) + if err != nil { + return nil, fmt.Errorf("Error creating reservation affinity: %s", err) } - if v := d.Get("key_revocation_action_type").(string); v != "" { - instance["keyRevocationActionType"] = v + var reservationAffinity *compute.ReservationAffinity + if reservationAffinityMap != nil { + reservationAffinity = &compute.ReservationAffinity{} + if err := tpgresource.Convert(reservationAffinityMap, reservationAffinity); err != nil { + return nil, fmt.Errorf("Error converting reservationAffinity: %s", err) + } } - if instanceEncryptionKey := expandComputeInstanceEncryptionKey(d); instanceEncryptionKey != nil { - instance["instanceEncryptionKey"] = instanceEncryptionKey + + instanceEncryptionKeyMap := expandComputeInstanceEncryptionKey(d) + var instanceEncryptionKey *compute.CustomerEncryptionKey + if instanceEncryptionKeyMap != nil { + instanceEncryptionKey = &compute.CustomerEncryptionKey{} + if err := tpgresource.Convert(instanceEncryptionKeyMap, instanceEncryptionKey); err != nil { + return nil, fmt.Errorf("Error converting instance_encryption_key: %s", err) + } } - {{- if ne $.TargetVersionName `ga` }} - if v := d.Get("erase_windows_vss_signature").(bool); v { - instance["eraseWindowsVssSignature"] = v + + // Create the instance information + instance := &compute.Instance{ + CanIpForward: d.Get("can_ip_forward").(bool), + Description: d.Get("description").(string), + Disks: disks, + MachineType: machineTypeUrl, + Name: d.Get("name").(string), + NetworkInterfaces: networkInterfaces, + NetworkPerformanceConfig: networkPerformanceConfig, + Tags: tags, + Labels: tpgresource.ExpandEffectiveLabels(d), + ServiceAccounts: expandServiceAccountsTyped(d.Get("service_account").([]interface{})), + GuestAccelerators: accels, + MinCpuPlatform: d.Get("min_cpu_platform").(string), + DeletionProtection: d.Get("deletion_protection").(bool), + Hostname: d.Get("hostname").(string), + ForceSendFields: []string{"CanIpForward", "DeletionProtection"}, + ResourcePolicies: tpgresource.ConvertStringArr(d.Get("resource_policies").([]interface{})), + ReservationAffinity: reservationAffinity, + KeyRevocationActionType: d.Get("key_revocation_action_type").(string), + InstanceEncryptionKey: instanceEncryptionKey, + {{- if ne $.TargetVersionName `ga` }} + EraseWindowsVssSignature: d.Get("erase_windows_vss_signature").(bool), + {{- end }} } - {{- end }} if wic := expandWorkloadIdentityConfig(d); wic != nil { - instance["workloadIdentityConfig"] = wic + instance.WorkloadIdentityConfig = &compute.WorkloadIdentityConfig{ + Identity: wic["identity"].(string), + IdentityCertificateEnabled: wic["identityCertificateEnabled"].(bool), + } } if cic := expandConfidentialInstanceConfig(d); cic != nil { - confidential := map[string]interface{}{} - if b, _ := cic["enableConfidentialCompute"].(bool); b { - confidential["enableConfidentialCompute"] = b + instance.ConfidentialInstanceConfig = &compute.ConfidentialInstanceConfig{ + EnableConfidentialCompute: cic["enableConfidentialCompute"].(bool), + ConfidentialInstanceType: cic["confidentialInstanceType"].(string), } - if t, _ := cic["confidentialInstanceType"].(string); t != "" { - confidential["confidentialInstanceType"] = t - } - instance["confidentialInstanceConfig"] = confidential } if sicMap := expandShieldedVmConfigs(d); sicMap != nil { - instance["shieldedInstanceConfig"] = sicMap + instance.ShieldedInstanceConfig = &compute.ShieldedInstanceConfig{ + EnableSecureBoot: sicMap["enableSecureBoot"].(bool), + EnableVtpm: sicMap["enableVtpm"].(bool), + EnableIntegrityMonitoring: sicMap["enableIntegrityMonitoring"].(bool), + ForceSendFields: []string{"EnableSecureBoot", "EnableVtpm", "EnableIntegrityMonitoring"}, + } } if dd := expandDisplayDevice(d); dd != nil { - instance["displayDevice"] = dd + enabled, _ := dd["enableDisplay"].(bool) + instance.DisplayDevice = &compute.DisplayDevice{ + EnableDisplay: enabled, + ForceSendFields: []string{"EnableDisplay"}, + } } return instance, nil } @@ -2067,8 +2094,7 @@ func waitUntilInstanceHasDesiredStatus(config *transport_tpg.Config, d *schema.R log.Printf("Error on InstanceStateRefresh: %s", err) return nil, "", err } - status, _ := instance["status"].(string) - return instance["id"], status, nil + return instance.Id, instance.Status, nil } stateChangeConf := retry.StateChangeConf{ Delay: 5 * time.Second, @@ -2120,7 +2146,10 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err {{- end }} log.Printf("[INFO] Requesting instance creation") - instanceBody := instance + instanceBody, err := tpgresource.ConvertToMap(instance) + if err != nil { + return fmt.Errorf("Error converting instance: %s", err) + } schedulingBody, err := expandScheduling(d.Get("scheduling")) if err != nil { return fmt.Errorf("Error creating scheduling: %s", err) @@ -2173,7 +2202,7 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err } // Store the ID now - d.SetId(fmt.Sprintf("projects/%s/zones/%s/instances/%s", project, z, d.Get("name").(string))) + d.SetId(fmt.Sprintf("projects/%s/zones/%s/instances/%s", project, z, instance.Name)) // Wait for the operation to complete waitErr := ComputeOperationWaitTime(config, res, project, "instance to create", userAgent, d.Timeout(schema.TimeoutCreate)) @@ -2184,7 +2213,7 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err } {{ if ne $.TargetVersionName `ga` -}} - err = computeInstanceAddSecurityPolicy(d, config, securityPolicies, project, z, userAgent, d.Get("name").(string)) + err = computeInstanceAddSecurityPolicy(d, config, securityPolicies, project, z, userAgent, instance.Name) if err != nil { return fmt.Errorf("Error creating instance while setting the security policies: %s", err) } @@ -2207,7 +2236,7 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err if err := tpgresource.SetResourceIdentityAttributes(d, map[string]interface{}{ "project": project, "zone": z, - "name": instance["name"], + "name": instance.Name, }); err != nil { return err } @@ -2234,8 +2263,7 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error savedNanos, hadNanos := d.GetOk("scheduling.0.graceful_shutdown.0.max_duration.0.nanos") {{- end }} - instanceZone, _ := instance["zone"].(string) - zone := tpgresource.GetResourceNameFromSelfLink(instanceZone) + zone := tpgresource.GetResourceNameFromSelfLink(instance.Zone) if err := populateComputeInstanceResourceData(d, instance, project, zone, config); err != nil { return err @@ -2258,7 +2286,10 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error // Fall back on internal ip if there is no external ip. This makes sense in the situation where // terraform is being used on a cloud instance and can therefore access the instances it creates // via their internal ips. - networkInterfacesRaw, _ := instance["networkInterfaces"].([]interface{}) + networkInterfacesRaw, err := networkInterfacesToInterface(instance.NetworkInterfaces) + if err != nil { + return err + } _, _, internalIP, externalIP, err := flattenNetworkInterfaces(d, config, networkInterfacesRaw) if err != nil { return err @@ -2275,7 +2306,10 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error {{ if ne $.TargetVersionName `ga` -}} // Workaround: restore nanos from state since the API doesn't persist it (see comment above). if hadNanos { - schedulingMap, _ := instance["scheduling"].(map[string]interface{}) + schedulingMap, err := tpgresource.ConvertToMap(instance.Scheduling) + if err != nil { + return fmt.Errorf("Error converting scheduling for nanos workaround: %s", err) + } scheduling := flattenScheduling(schedulingMap) graceful_shutdown := scheduling[0]["graceful_shutdown"].([]interface{})[0].(map[string]interface{}) max_duration := graceful_shutdown["max_duration"].([]interface{})[0].(map[string]interface{}) @@ -2289,8 +2323,7 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error } {{- end }} - instanceName, _ := instance["name"].(string) - d.SetId(fmt.Sprintf("projects/%s/zones/%s/instances/%s", project, zone, instanceName)) + d.SetId(fmt.Sprintf("projects/%s/zones/%s/instances/%s", project, zone, instance.Name)) if err := tpgresource.DeletionPolicyReadDefault(d, config, "DELETE"); err != nil { return err @@ -2299,7 +2332,7 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error if err := tpgresource.SetResourceIdentityAttributes(d, map[string]interface{}{ "project": project, "zone": zone, - "name": instance["name"], + "name": instance.Name, }); err != nil { return err } @@ -2307,33 +2340,45 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error return nil } -func populateComputeInstanceResourceData(d *schema.ResourceData, instance map[string]interface{}, project, zone string, config *transport_tpg.Config) error { - metadataMap, _ := instance["metadata"].(map[string]interface{}) +func populateComputeInstanceResourceData(d *schema.ResourceData, instance *compute.Instance, project, zone string, config *transport_tpg.Config) error { + var metadataMap map[string]interface{} + if instance.Metadata != nil { + var err error + metadataMap, err = tpgresource.ConvertToMap(instance.Metadata) + if err != nil { + return fmt.Errorf("Error converting metadata: %s", err) + } + } if err := d.Set("metadata", flattenMetadataBeta(metadataMap)); err != nil { return fmt.Errorf("Error setting metadata: %s", err) } - var metadataFingerprint string - if metadataMap != nil { - metadataFingerprint, _ = metadataMap["fingerprint"].(string) - } - if err := d.Set("metadata_fingerprint", metadataFingerprint); err != nil { + if err := d.Set("metadata_fingerprint", instance.Metadata.Fingerprint); err != nil { return fmt.Errorf("Error setting metadata_fingerprint: %s", err) } - if err := d.Set("can_ip_forward", instance["canIpForward"]); err != nil { + if err := d.Set("can_ip_forward", instance.CanIpForward); err != nil { return fmt.Errorf("Error setting can_ip_forward: %s", err) } - machineType, _ := instance["machineType"].(string) - if err := d.Set("machine_type", tpgresource.GetResourceNameFromSelfLink(machineType)); err != nil { + if err := d.Set("machine_type", tpgresource.GetResourceNameFromSelfLink(instance.MachineType)); err != nil { return fmt.Errorf("Error setting machine_type: %s", err) } - npcMap, _ := instance["networkPerformanceConfig"].(map[string]interface{}) + var npcMap map[string]interface{} + if instance.NetworkPerformanceConfig != nil { + var err error + npcMap, err = tpgresource.ConvertToMap(instance.NetworkPerformanceConfig) + if err != nil { + return fmt.Errorf("Error converting network_performance_config: %s", err) + } + } if err := d.Set("network_performance_config", flattenNetworkPerformanceConfig(npcMap)); err != nil { return err } // Set the networks - networkInterfacesRaw, _ := instance["networkInterfaces"].([]interface{}) + networkInterfacesRaw, err := networkInterfacesToInterface(instance.NetworkInterfaces) + if err != nil { + return err + } networkInterfaces, _, _, _, err := flattenNetworkInterfaces(d, config, networkInterfacesRaw) if err != nil { return err @@ -2343,36 +2388,29 @@ func populateComputeInstanceResourceData(d *schema.ResourceData, instance map[st } // Set the tags fingerprint if there is one. - if tags, ok := instance["tags"].(map[string]interface{}); ok { - if err := d.Set("tags_fingerprint", tags["fingerprint"]); err != nil { + if instance.Tags != nil { + if err := d.Set("tags_fingerprint", instance.Tags.Fingerprint); err != nil { return fmt.Errorf("Error setting tags_fingerprint: %s", err) } - items, _ := tags["items"].([]interface{}) - if err := d.Set("tags", items); err != nil { + if err := d.Set("tags", tpgresource.ConvertStringArrToInterface(instance.Tags.Items)); err != nil { return fmt.Errorf("Error setting tags: %s", err) } } - labels := map[string]string{} - if raw, ok := instance["labels"].(map[string]interface{}); ok { - for k, v := range raw { - labels[k], _ = v.(string) - } - } - if err := tpgresource.SetLabels(labels, d, "labels"); err != nil { + if err := tpgresource.SetLabels(instance.Labels, d, "labels"); err != nil { return err } - if err := tpgresource.SetLabels(labels, d, "terraform_labels"); err != nil { + if err := tpgresource.SetLabels(instance.Labels, d, "terraform_labels"); err != nil { return err } - if err := d.Set("effective_labels", labels); err != nil { + if err := d.Set("effective_labels", instance.Labels); err != nil { return err } - if labelFingerprint, _ := instance["labelFingerprint"].(string); labelFingerprint != "" { - if err := d.Set("label_fingerprint", labelFingerprint); err != nil { + if instance.LabelFingerprint != "" { + if err := d.Set("label_fingerprint", instance.LabelFingerprint); err != nil { return fmt.Errorf("Error setting label_fingerprint: %s", err) } } @@ -2406,29 +2444,23 @@ func populateComputeInstanceResourceData(d *schema.ResourceData, instance map[st attachedDisks := make([]map[string]interface{}, d.Get("attached_disk.#").(int)) scratchDisks := []map[string]interface{}{} - instanceDisks, _ := instance["disks"].([]interface{}) - for _, rawDisk := range instanceDisks { - disk, ok := rawDisk.(map[string]interface{}) - if !ok { - continue - } - diskSource, _ := disk["source"].(string) - if isBoot, _ := disk["boot"].(bool); isBoot { + for _, disk := range instance.Disks { + if disk.Boot { if err := d.Set("boot_disk", flattenBootDisk(d, disk, config)); err != nil { return fmt.Errorf("Error setting boot_disk: %s", err) } - } else if diskType, _ := disk["type"].(string); diskType == "SCRATCH" { + } else if disk.Type == "SCRATCH" { scratchDisks = append(scratchDisks, flattenScratchDisk(disk)) } else { var sourceLink string - if strings.Contains(diskSource, "regions/") { - source, err := tpgresource.ParseRegionDiskFieldValue(diskSource, d, config) + if strings.Contains(disk.Source, "regions/") { + source, err := tpgresource.ParseRegionDiskFieldValue(disk.Source, d, config) if err != nil { return err } sourceLink = source.RelativeLink() } else { - source, err := tpgresource.ParseDiskFieldValue(diskSource, d, config) + source, err := tpgresource.ParseDiskFieldValue(disk.Source, d, config) if err != nil { return err } @@ -2436,11 +2468,11 @@ func populateComputeInstanceResourceData(d *schema.ResourceData, instance map[st } adIndex, inConfig := attachedDiskSources[sourceLink] di := map[string]interface{}{ - "source": tpgresource.ConvertSelfLinkToV1(diskSource), - "device_name": disk["deviceName"], - "mode": disk["mode"], + "source": tpgresource.ConvertSelfLinkToV1(disk.Source), + "device_name": disk.DeviceName, + "mode": disk.Mode, } - if key, ok := disk["diskEncryptionKey"].(map[string]interface{}); ok { + if key := disk.DiskEncryptionKey; key != nil { if inConfig { rsaKey := d.Get(fmt.Sprintf("attached_disk.%d.disk_encryption_key_rsa", adIndex)) if rsaKey != "" { @@ -2454,13 +2486,13 @@ func populateComputeInstanceResourceData(d *schema.ResourceData, instance map[st di["disk_encryption_service_account"] = serviceAccount } } - if kmsKeyName, _ := key["kmsKeyName"].(string); kmsKeyName != "" { + if key.KmsKeyName != "" { // The response for crypto keys often includes the version of the key which needs to be removed // format: projects//locations//keyRings//cryptoKeys//cryptoKeyVersions/1 - di["kms_key_self_link"] = strings.Split(kmsKeyName, "/cryptoKeyVersions")[0] + di["kms_key_self_link"] = strings.Split(disk.DiskEncryptionKey.KmsKeyName, "/cryptoKeyVersions")[0] } - if sha256, _ := key["sha256"].(string); sha256 != "" { - di["disk_encryption_key_sha256"] = sha256 + if key.Sha256 != "" { + di["disk_encryption_key_sha256"] = key.Sha256 } } @@ -2478,7 +2510,7 @@ func populateComputeInstanceResourceData(d *schema.ResourceData, instance map[st } } - if err := d.Set("resource_policies", instance["resourcePolicies"]); err != nil { + if err := d.Set("resource_policies", instance.ResourcePolicies); err != nil { return fmt.Errorf("Error setting resource_policies: %s", err) } @@ -2490,8 +2522,7 @@ func populateComputeInstanceResourceData(d *schema.ResourceData, instance map[st ads = append(ads, ad) } } - instanceServiceAccounts, _ := instance["serviceAccounts"].([]interface{}) - if err := d.Set("service_account", flattenServiceAccounts(instanceServiceAccounts)); err != nil { + if err := d.Set("service_account", flattenServiceAccounts(serviceAccountsToInterface(instance.ServiceAccounts))); err != nil { return fmt.Errorf("Error setting service_account: %s", err) } if err := d.Set("attached_disk", ads); err != nil { @@ -2502,14 +2533,20 @@ func populateComputeInstanceResourceData(d *schema.ResourceData, instance map[st } {{ if eq $.TargetVersionName `ga` -}} - schedulingMap, _ := instance["scheduling"].(map[string]interface{}) + schedulingMap, err := tpgresource.ConvertToMap(instance.Scheduling) + if err != nil { + return fmt.Errorf("Error converting scheduling: %s", err) + } if err := d.Set("scheduling", flattenScheduling(schedulingMap)); err != nil { return fmt.Errorf("Error setting scheduling: %s", err) } {{ else -}} // Workaroud: API doesn't update the scheduling.graceful_shutdown.max_duration.nanos field. // To avoid diff, we need to set the value from the state not from API response. - schedulingMap, _ := instance["scheduling"].(map[string]interface{}) + schedulingMap, err := tpgresource.ConvertToMap(instance.Scheduling) + if err != nil { + return fmt.Errorf("Error converting scheduling: %s", err) + } scheduling := flattenScheduling(schedulingMap) if nanos, ok := d.GetOk("scheduling.0.graceful_shutdown.0.max_duration.0.nanos"); ok { graceful_shutdown := scheduling[0]["graceful_shutdown"].([]interface{})[0].(map[string]interface{}) @@ -2524,35 +2561,43 @@ func populateComputeInstanceResourceData(d *schema.ResourceData, instance map[st } {{- end }} - instanceGuestAccelerators, _ := instance["guestAccelerators"].([]interface{}) - if err := d.Set("guest_accelerator", flattenGuestAccelerators(instanceGuestAccelerators)); err != nil { + if err := d.Set("guest_accelerator", flattenGuestAccelerators(guestAcceleratorsToInterface(instance.GuestAccelerators))); err != nil { return fmt.Errorf("Error setting guest_accelerator: %s", err) } - shieldedVmConfigMap, _ := instance["shieldedInstanceConfig"].(map[string]interface{}) + var shieldedVmConfigMap map[string]interface{} + if sic := instance.ShieldedInstanceConfig; sic != nil { + shieldedVmConfigMap = map[string]interface{}{ + "enableSecureBoot": sic.EnableSecureBoot, + "enableVtpm": sic.EnableVtpm, + "enableIntegrityMonitoring": sic.EnableIntegrityMonitoring, + } + } if err := d.Set("shielded_instance_config", flattenShieldedVmConfig(shieldedVmConfigMap)); err != nil { return fmt.Errorf("Error setting shielded_instance_config: %s", err) } - displayDeviceMap, _ := instance["displayDevice"].(map[string]interface{}) + var displayDeviceMap map[string]interface{} + if instance.DisplayDevice != nil { + displayDeviceMap = map[string]interface{}{"enableDisplay": instance.DisplayDevice.EnableDisplay} + } if err := d.Set("enable_display", flattenEnableDisplay(displayDeviceMap)); err != nil { return fmt.Errorf("Error setting enable_display: %s", err) } - if err := d.Set("cpu_platform", instance["cpuPlatform"]); err != nil { + if err := d.Set("cpu_platform", instance.CpuPlatform); err != nil { return fmt.Errorf("Error setting cpu_platform: %s", err) } - if err := d.Set("min_cpu_platform", instance["minCpuPlatform"]); err != nil { + if err := d.Set("min_cpu_platform", instance.MinCpuPlatform); err != nil { return fmt.Errorf("Error setting min_cpu_platform: %s", err) } - if err := d.Set("deletion_protection", instance["deletionProtection"]); err != nil { + if err := d.Set("deletion_protection", instance.DeletionProtection); err != nil { return fmt.Errorf("Error setting deletion_protection: %s", err) } - selfLink, _ := instance["selfLink"].(string) - if err := d.Set("self_link", tpgresource.ConvertSelfLinkToV1(selfLink)); err != nil { + if err := d.Set("self_link", tpgresource.ConvertSelfLinkToV1(instance.SelfLink)); err != nil { return fmt.Errorf("Error setting self_link: %s", err) } - if err := d.Set("instance_id", fmt.Sprintf("%d", getInt(instance["id"]))); err != nil { + if err := d.Set("instance_id", fmt.Sprintf("%d", instance.Id)); err != nil { return fmt.Errorf("Error setting instance_id: %s", err) } - if err := d.Set("creation_timestamp", instance["creationTimestamp"]); err != nil { + if err := d.Set("creation_timestamp", instance.CreationTimestamp); err != nil { return fmt.Errorf("Error setting creation_timestamp: %s", err) } if err := d.Set("project", project); err != nil { @@ -2561,46 +2606,71 @@ func populateComputeInstanceResourceData(d *schema.ResourceData, instance map[st if err := d.Set("zone", zone); err != nil { return fmt.Errorf("Error setting zone: %s", err) } - if err := d.Set("name", instance["name"]); err != nil { + if err := d.Set("name", instance.Name); err != nil { return fmt.Errorf("Error setting name: %s", err) } - if err := d.Set("description", instance["description"]); err != nil { + if err := d.Set("description", instance.Description); err != nil { return fmt.Errorf("Error setting description: %s", err) } - if err := d.Set("hostname", instance["hostname"]); err != nil { + if err := d.Set("hostname", instance.Hostname); err != nil { return fmt.Errorf("Error setting hostname: %s", err) } - if err := d.Set("current_status", instance["status"]); err != nil { + if err := d.Set("current_status", instance.Status); err != nil { return fmt.Errorf("Error setting current_status: %s", err) } - if cicMap, ok := instance["confidentialInstanceConfig"].(map[string]interface{}); ok { + if instance.ConfidentialInstanceConfig != nil { + cicMap, err := tpgresource.ConvertToMap(instance.ConfidentialInstanceConfig) + if err != nil { + return fmt.Errorf("Error converting confidential_instance_config: %s", err) + } if err := d.Set("confidential_instance_config", flattenConfidentialInstanceConfig(cicMap)); err != nil { return fmt.Errorf("Error setting confidential_instance_config: %s", err) } } - amfMap, _ := instance["advancedMachineFeatures"].(map[string]interface{}) + amfMap, err := tpgresource.ConvertToMap(instance.AdvancedMachineFeatures) + if err != nil { + return fmt.Errorf("Error converting advanced_machine_features: %s", err) + } if err := d.Set("advanced_machine_features", flattenAdvancedMachineFeatures(amfMap)); err != nil { return fmt.Errorf("Error setting advanced_machine_features: %s", err) } if d.Get("desired_status") != "" { - if err := d.Set("desired_status", instance["status"]); err != nil { + if err := d.Set("desired_status", instance.Status); err != nil { return fmt.Errorf("Error setting desired_status: %s", err) } } - reservationAffinityMap, _ := instance["reservationAffinity"].(map[string]interface{}) + var reservationAffinityMap map[string]interface{} + if instance.ReservationAffinity != nil { + var err error + reservationAffinityMap, err = tpgresource.ConvertToMap(instance.ReservationAffinity) + if err != nil { + return fmt.Errorf("Error converting reservation_affinity: %s", err) + } + } if err := d.Set("reservation_affinity", flattenReservationAffinity(reservationAffinityMap)); err != nil { return fmt.Errorf("Error setting reservation_affinity: %s", err) } - if err := d.Set("key_revocation_action_type", instance["keyRevocationActionType"]); err != nil { + if err := d.Set("key_revocation_action_type", instance.KeyRevocationActionType); err != nil { return fmt.Errorf("Error setting key_revocation_action_type: %s", err) } - instanceEncryptionKeyMap, _ := instance["instanceEncryptionKey"].(map[string]interface{}) + var instanceEncryptionKeyMap map[string]interface{} + if instance.InstanceEncryptionKey != nil { + var err error + instanceEncryptionKeyMap, err = tpgresource.ConvertToMap(instance.InstanceEncryptionKey) + if err != nil { + return fmt.Errorf("Error converting instance_encryption_key: %s", err) + } + } if err := d.Set("instance_encryption_key", flattenComputeInstanceEncryptionKey(instanceEncryptionKeyMap)); err != nil { return fmt.Errorf("Error setting instance_encryption_key: %s", err) } {{- if ne $.TargetVersionName `ga` }} - if partnerMetadataMap, ok := instance["partnerMetadata"].(map[string]interface{}); ok { + if instance.PartnerMetadata != nil { + partnerMetadataMap, err := tpgresource.ConvertToMap(instance.PartnerMetadata) + if err != nil { + return fmt.Errorf("Error converting partner metadata: %s", err) + } partnerMetadata, err := flattenPartnerMetadata(convertPartnerMetadataFromCompute(partnerMetadataMap)) if err != nil { return fmt.Errorf("Error parsing partner metadata: %s", err) @@ -2666,7 +2736,10 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err if err != nil { return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("Instance %s", d.Get("name").(string))) } - instance := instanceMap + instance := &compute.Instance{} + if err := tpgresource.Convert(instanceMap, instance); err != nil { + return fmt.Errorf("Error parsing instance response: %s", err) + } // Enable partial mode for the resource since it is possible d.Partial(true) @@ -2813,7 +2886,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err {{ end }} if d.HasChange("tags") { - tagsBody := resourceInstanceTagsOmitEmpty(d) + tagsBody := resourceInstanceTags(d) url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/setTags") if err != nil { return fmt.Errorf("Error generating URL: %s", err) @@ -2839,12 +2912,11 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err if d.HasChange("effective_labels") { labels := tpgresource.ExpandEffectiveLabels(d) labelFingerprint := d.Get("label_fingerprint").(string) - labelsBody := map[string]interface{}{} - if len(labels) > 0 { - labelsBody["labels"] = labels - } - if labelFingerprint != "" { - labelsBody["labelFingerprint"] = labelFingerprint + req := compute.InstancesSetLabelsRequest{Labels: labels, LabelFingerprint: labelFingerprint} + + labelsBody, err := tpgresource.ConvertToMap(&req) + if err != nil { + return fmt.Errorf("Error converting labels: %s", err) } url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/setLabels") if err != nil { @@ -2912,10 +2984,13 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err } if d.HasChange("resource_policies") { - instanceResourcePolicies, _ := instance["resourcePolicies"].([]interface{}) - if len(instanceResourcePolicies) > 0 { - body := map[string]interface{}{"resourcePolicies": instanceResourcePolicies} + if len(instance.ResourcePolicies) > 0 { + req := compute.InstancesRemoveResourcePoliciesRequest{ResourcePolicies: instance.ResourcePolicies} + body, err := tpgresource.ConvertToMap(&req) + if err != nil { + return fmt.Errorf("Error converting remove resource policies request: %s", err) + } url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/removeResourcePolicies") if err != nil { return fmt.Errorf("Error generating URL: %s", err) @@ -2940,8 +3015,12 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err resourcePolicies := tpgresource.ConvertStringArr(d.Get("resource_policies").([]interface{})) if len(resourcePolicies) > 0 { - body := map[string]interface{}{"resourcePolicies": resourcePolicies} + req := compute.InstancesAddResourcePoliciesRequest{ResourcePolicies: resourcePolicies} + body, err := tpgresource.ConvertToMap(&req) + if err != nil { + return fmt.Errorf("Error converting add resource policies request: %s", err) + } url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/addResourcePolicies") if err != nil { return fmt.Errorf("Error generating URL: %s", err) @@ -2996,25 +3075,24 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err } } - networkInterfaces, err := expandNetworkInterfaces(d, config) + networkInterfaces, err := expandNetworkInterfacesTyped(d, config) if err != nil { return fmt.Errorf("Error getting network interface from config: %s", err) } - instNics, _ := instance["networkInterfaces"].([]interface{}) // Sanity check - if len(networkInterfaces) != len(instNics) { - return fmt.Errorf("Instance had unexpected number of network interfaces: %d", len(instNics)) + if len(networkInterfaces) != len(instance.NetworkInterfaces) { + return fmt.Errorf("Instance had unexpected number of network interfaces: %d", len(instance.NetworkInterfaces)) } {{ if ne $.TargetVersionName `ga` -}} updateSecurityPolicy := false - for i := 0; i < len(instNics); i++ { + for i := 0; i < len(instance.NetworkInterfaces); i++ { prefix := fmt.Sprintf("network_interface.%d", i) // check if sec policy has been changed // check if access config has been changed because it may be deleted and needs to be re-created. if d.HasChange(prefix+".security_policy") || d.HasChange(prefix+".access_config") || d.HasChange(prefix+".ipv6_access_config") { - if instStatus, _ := instance["status"].(string); instStatus != "RUNNING" { + if instance.Status != "RUNNING" { return fmt.Errorf("Error to update security policy because the current instance status must be \"RUNNING\". The security policy or some access config may have changed which requires the security policy to be re-applied") } updateSecurityPolicy = true @@ -3034,16 +3112,16 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err var updatesToNIWhileStopped []func(inst map[string]interface{}) error for i := 0; i < len(networkInterfaces); i++ { prefix := fmt.Sprintf("network_interface.%d", i) - networkInterface, _ := networkInterfaces[i].(map[string]interface{}) - instNetworkInterface, _ := instNics[i].(map[string]interface{}) + networkInterface := networkInterfaces[i] + instNetworkInterface := instance.NetworkInterfaces[i] networkName := d.Get(prefix + ".name").(string) - subnetwork, _ := networkInterface["subnetwork"].(string) + subnetwork := networkInterface.Subnetwork updateDuringStop := d.HasChange(prefix+".subnetwork") || d.HasChange(prefix+".network") || d.HasChange(prefix+".subnetwork_project") // Sanity check - if instNiName, _ := instNetworkInterface["name"].(string); networkName != instNiName { - return fmt.Errorf("Instance networkInterface had unexpected name: %s", instNiName) + if networkName != instNetworkInterface.Name { + return fmt.Errorf("Instance networkInterface had unexpected name: %s", instNetworkInterface.Name) } // On creation the network is inferred if only subnetwork is given. @@ -3077,7 +3155,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err if err != nil { return fmt.Errorf("Cannot determine self_link for network %q: %s", networkSelfLink, err) } - networkInterface["network"] = nf.RelativeLink() + networkInterface.Network = nf.RelativeLink() } } @@ -3089,19 +3167,25 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err // necessary, and also add before removing. // Delete current access configs - instNiMap := instNetworkInterface - err = computeInstanceDeleteAccessConfigs(d, config, instNiMap, project, zone, userAgent, d.Get("name").(string)) + instNiMap, err := tpgresource.ConvertToMap(instNetworkInterface) + if err != nil { + return fmt.Errorf("Error converting network interface to map: %w", err) + } + err = computeInstanceDeleteAccessConfigs(d, config, instNiMap, project, zone, userAgent, instance.Name) if err != nil { return err } // Create new ones - accessConfigs, _ := networkInterface["accessConfigs"].([]interface{}) - acMaps := make([]interface{}, len(accessConfigs)) - for j, ac := range accessConfigs { - acMaps[j] = ac + acMaps := make([]interface{}, len(networkInterface.AccessConfigs)) + for j, ac := range networkInterface.AccessConfigs { + acMap, err := tpgresource.ConvertToMap(ac) + if err != nil { + return fmt.Errorf("Error converting access config to map: %w", err) + } + acMaps[j] = acMap } - err = computeInstanceAddAccessConfigs(d, config, instNiMap, acMaps, project, zone, userAgent, d.Get("name").(string)) + err = computeInstanceAddAccessConfigs(d, config, instNiMap, acMaps, project, zone, userAgent, instance.Name) if err != nil { return err } @@ -3121,23 +3205,30 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err if err != nil { return err } - instance = freshMap - instNics, _ = instance["networkInterfaces"].([]interface{}) - instNetworkInterface, _ = instNics[i].(map[string]interface{}) + if err := tpgresource.Convert(freshMap, instance); err != nil { + return fmt.Errorf("Error parsing instance response: %s", err) + } + instNetworkInterface = instance.NetworkInterfaces[i] } if !updateDuringStop && d.HasChange(prefix+".alias_ip_range") { // Alias IP ranges cannot be updated; they must be removed and then added // unless you are changing subnetwork/network - if instAliasIpRanges, _ := instNetworkInterface["aliasIpRanges"].([]interface{}); len(instAliasIpRanges) > 0 { - oldNiMap := instNetworkInterface - newNiMap := networkInterface + if len(instNetworkInterface.AliasIpRanges) > 0 { + oldNiMap, err := tpgresource.ConvertToMap(instNetworkInterface) + if err != nil { + return fmt.Errorf("Error converting network interface to map: %w", err) + } + newNiMap, err := tpgresource.ConvertToMap(networkInterface) + if err != nil { + return fmt.Errorf("Error converting network interface to map: %w", err) + } aliasRanges := []interface{}{} if commonAliasIpRanges := CheckForCommonAliasIp(oldNiMap, newNiMap); len(commonAliasIpRanges) > 0 { aliasRanges = commonAliasIpRanges } niBody := map[string]interface{}{ - "fingerprint": instNetworkInterface["fingerprint"], + "fingerprint": instNetworkInterface.Fingerprint, "aliasIpRanges": aliasRanges, } url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/updateNetworkInterface") @@ -3178,16 +3269,19 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err if err != nil { return err } - instance = freshMap - instNics, _ = instance["networkInterfaces"].([]interface{}) - instNetworkInterface, _ = instNics[i].(map[string]interface{}) + if err := tpgresource.Convert(freshMap, instance); err != nil { + return fmt.Errorf("Error parsing instance response: %s", err) + } + instNetworkInterface = instance.NetworkInterfaces[i] } - niBody := map[string]interface{}{ - "fingerprint": instNetworkInterface["fingerprint"], + networkInterfacePatchObj := &compute.NetworkInterface{ + AliasIpRanges: networkInterface.AliasIpRanges, + Fingerprint: instNetworkInterface.Fingerprint, } - if air, ok := networkInterface["aliasIpRanges"].([]interface{}); ok && len(air) > 0 { - niBody["aliasIpRanges"] = air + niBody, err := tpgresource.ConvertToMap(networkInterfacePatchObj) + if err != nil { + return errwrap.Wrapf("Error converting network interface: {{"{{"}}err{{"}}"}}", err) } url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/updateNetworkInterface") if err != nil { @@ -3218,110 +3312,61 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err if !updateDuringStop && d.HasChange(prefix+".alias_ipv6_range") { // Alias IPv6 ranges cannot be updated; they must be removed and then added // unless you are changing subnetwork/network - if instAliasIpv6Ranges, _ := instNetworkInterface["aliasIpv6Ranges"].([]interface{}); len(instAliasIpv6Ranges) > 0 { - ni := map[string]interface{}{ - "fingerprint": instNetworkInterface["fingerprint"], - "aliasIpv6Ranges": []interface{}{}, + if len(instNetworkInterface.AliasIpv6Ranges) > 0 { + ni := &compute.NetworkInterface{ + Fingerprint: instNetworkInterface.Fingerprint, + ForceSendFields: []string{"AliasIpv6Ranges"}, } if commonAliasIpv6Ranges := CheckForCommonAliasIpv6(instNetworkInterface, networkInterface); len(commonAliasIpv6Ranges) > 0 { - ni["aliasIpv6Ranges"] = commonAliasIpv6Ranges - } - url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/updateNetworkInterface") - if err != nil { - return errwrap.Wrapf("Error generating URL: {{"{{"}}err{{"}}"}}", err) - } - url, err = transport_tpg.AddQueryParams(url, map[string]string{"networkInterface": networkName}) - if err != nil { - return err + ni.AliasIpv6Ranges = commonAliasIpv6Ranges } - res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "PATCH", - Project: project, - RawURL: url, - UserAgent: userAgent, - Body: ni, - }) + op, err := NewClient(config, userAgent).Instances.UpdateNetworkInterface(project, zone, instance.Name, networkName, ni).Do() if err != nil { return errwrap.Wrapf("Error removing alias_ipv6_range: {{"{{"}}err{{"}}"}}", err) } - opErr := ComputeOperationWaitTime(config, res, project, "updating alias ipv6 ranges", userAgent, d.Timeout(schema.TimeoutUpdate)) + opErr := ComputeOperationWaitTime(config, op, project, "updating alias ipv6 ranges", userAgent, d.Timeout(schema.TimeoutUpdate)) if opErr != nil { return opErr } // re-read fingerprint - freshUrl, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}") - if err != nil { - return err - } - instance, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "GET", - Project: project, - RawURL: freshUrl, - UserAgent: userAgent, - }) + instance, err = NewClient(config, userAgent).Instances.Get(project, zone, instance.Name).Do() if err != nil { return err } - instNics, _ = instance["networkInterfaces"].([]interface{}) - instNetworkInterface, _ = instNics[i].(map[string]interface{}) + instNetworkInterface = instance.NetworkInterfaces[i] } - niBody := map[string]interface{}{ - "fingerprint": instNetworkInterface["fingerprint"], + networkInterfacePatchObj := &compute.NetworkInterface{ + AliasIpv6Ranges: networkInterface.AliasIpv6Ranges, + Fingerprint: instNetworkInterface.Fingerprint, } - if air, ok := networkInterface["aliasIpv6Ranges"].([]interface{}); ok && len(air) > 0 { - niBody["aliasIpv6Ranges"] = air - } - url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/updateNetworkInterface") - if err != nil { - return errwrap.Wrapf("Error generating URL: {{"{{"}}err{{"}}"}}", err) - } - url, err = transport_tpg.AddQueryParams(url, map[string]string{"networkInterface": networkName}) - if err != nil { - return err - } - res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "PATCH", - Project: project, - RawURL: url, - UserAgent: userAgent, - Body: niBody, - }) + updateCall := NewClient(config, userAgent).Instances.UpdateNetworkInterface(project, zone, instance.Name, networkName, networkInterfacePatchObj).Do + op, err := updateCall() if err != nil { return errwrap.Wrapf("Error updating network interface: {{`{{`}}err{{`}}`}}", err) } - opErr := ComputeOperationWaitTime(config, res, project, "network interface to update", userAgent, d.Timeout(schema.TimeoutUpdate)) + opErr := ComputeOperationWaitTime(config, op, project, "network interface to update", userAgent, d.Timeout(schema.TimeoutUpdate)) if opErr != nil { return opErr } // re-read fingerprint - freshUrl, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}") - if err != nil { - return err - } - instance, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "GET", - Project: project, - RawURL: freshUrl, - UserAgent: userAgent, - }) + instance, err = NewClient(config, userAgent).Instances.Get(project, zone, instance.Name).Do() if err != nil { return err } - instNics, _ = instance["networkInterfaces"].([]interface{}) - instNetworkInterface, _ = instNics[i].(map[string]interface{}) + instNetworkInterface = instance.NetworkInterfaces[i] } {{- end }} if !updateDuringStop && d.HasChange(prefix+".stack_type") { - niBody := map[string]interface{}{ - "stackType": d.Get(prefix+".stack_type").(string), - "fingerprint": instNetworkInterface["fingerprint"], + networkInterfacePatchObj := &compute.NetworkInterface{ + StackType: d.Get(prefix+".stack_type").(string), + Fingerprint: instNetworkInterface.Fingerprint, + } + niBody, err := tpgresource.ConvertToMap(networkInterfacePatchObj) + if err != nil { + return errwrap.Wrapf("Error converting network interface: {{"{{"}}err{{"}}"}}", err) } url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/updateNetworkInterface") if err != nil { @@ -3350,9 +3395,13 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err if !updateDuringStop && d.HasChange(prefix+".igmp_query") { - niBody := map[string]interface{}{ - "igmpQuery": d.Get(prefix+".igmp_query").(string), - "fingerprint": instNetworkInterface["fingerprint"], + networkInterfacePatchObj := &compute.NetworkInterface{ + IgmpQuery: d.Get(prefix+".igmp_query").(string), + Fingerprint: instNetworkInterface.Fingerprint, + } + niBody, err := tpgresource.ConvertToMap(networkInterfacePatchObj) + if err != nil { + return errwrap.Wrapf("Error converting network interface: {{"{{"}}err{{"}}"}}", err) } url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/updateNetworkInterface") if err != nil { @@ -3381,9 +3430,13 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err if !updateDuringStop && d.HasChange(prefix+".ipv6_address") { - niBody := map[string]interface{}{ - "ipv6Address": d.Get(prefix+".ipv6_address").(string), - "fingerprint": instNetworkInterface["fingerprint"], + networkInterfacePatchObj := &compute.NetworkInterface{ + Ipv6Address: d.Get(prefix+".ipv6_address").(string), + Fingerprint: instNetworkInterface.Fingerprint, + } + niBody, err := tpgresource.ConvertToMap(networkInterfacePatchObj) + if err != nil { + return errwrap.Wrapf("Error converting network interface: {{"{{"}}err{{"}}"}}", err) } url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/updateNetworkInterface") if err != nil { @@ -3412,9 +3465,13 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err if !updateDuringStop && d.HasChange(prefix+".internal_ipv6_prefix_length") { - niBody := map[string]interface{}{ - "internalIpv6PrefixLength": d.Get(prefix+".internal_ipv6_prefix_length").(int64), - "fingerprint": instNetworkInterface["fingerprint"], + networkInterfacePatchObj := &compute.NetworkInterface{ + InternalIpv6PrefixLength: d.Get(prefix+".internal_ipv6_prefix_length").(int64), + Fingerprint: instNetworkInterface.Fingerprint, + } + niBody, err := tpgresource.ConvertToMap(networkInterfacePatchObj) + if err != nil { + return errwrap.Wrapf("Error converting network interface: {{"{{"}}err{{"}}"}}", err) } url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/updateNetworkInterface") if err != nil { @@ -3443,27 +3500,27 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err if updateDuringStop { // Lets be explicit about what we are changing in the patch call - niPatchMap := map[string]interface{}{ - "network": networkInterface["network"], - "subnetwork": networkInterface["subnetwork"], - "aliasIpRanges": networkInterface["aliasIpRanges"], -{{- if ne $.TargetVersionName "ga" }} - "aliasIpv6Ranges": networkInterface["aliasIpv6Ranges"], + networkInterfacePatchObj := &compute.NetworkInterface{ + Network: networkInterface.Network, + Subnetwork: networkInterface.Subnetwork, + AliasIpRanges: networkInterface.AliasIpRanges, +{{ if ne $.TargetVersionName `ga` -}} + AliasIpv6Ranges: networkInterface.AliasIpv6Ranges, {{- end }} } // network_ip can be inferred if not declared. Let's only patch if it's being changed by user // otherwise this could fail if the network ip is not compatible with the new Subnetwork/Network. if d.HasChange(prefix + ".network_ip") { - niPatchMap["networkIP"] = networkInterface["networkIP"] + networkInterfacePatchObj.NetworkIP = networkInterface.NetworkIP } if d.HasChange(prefix+".internal_ipv6_prefix_length") { - niPatchMap["ipv6Address"] = networkInterface["ipv6Address"] + networkInterfacePatchObj.Ipv6Address = networkInterface.Ipv6Address } if d.HasChange(prefix+".ipv6_address") { - niPatchMap["ipv6Address"] = networkInterface["ipv6Address"] + networkInterfacePatchObj.Ipv6Address = networkInterface.Ipv6Address } // Access config can run into some issues since we can't tell the difference between @@ -3473,9 +3530,20 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err // configs if we notice the configuration (user intent) changes. accessConfigsHaveChanged := d.HasChange(prefix + ".access_config") - acMaps, _ := networkInterface["accessConfigs"].([]interface{}) + acMaps := make([]interface{}, len(networkInterface.AccessConfigs)) + for j, ac := range networkInterface.AccessConfigs { + acMap, err := tpgresource.ConvertToMap(ac) + if err != nil { + return fmt.Errorf("Error converting access config to map: %w", err) + } + acMaps[j] = acMap + } + niPatchMap, err := tpgresource.ConvertToMap(networkInterfacePatchObj) + if err != nil { + return fmt.Errorf("Error converting network interface patch object to map: %w", err) + } - updateCall := computeInstanceCreateUpdateWhileStoppedCall(d, config, niPatchMap, acMaps, accessConfigsHaveChanged, i, project, zone, userAgent, d.Get("name").(string)) + updateCall := computeInstanceCreateUpdateWhileStoppedCall(d, config, niPatchMap, acMaps, accessConfigsHaveChanged, i, project, zone, userAgent, instance.Name) updatesToNIWhileStopped = append(updatesToNIWhileStopped, updateCall) } } @@ -3492,16 +3560,14 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err if d.HasChange("boot_disk.0.initialize_params") { var locationType, location string - if diskRegion, _ := disk["region"].(string); diskRegion != "" { + if disk.Region != "" { locationType = "regions" - location = tpgresource.GetResourceNameFromSelfLink(diskRegion) + location = tpgresource.GetResourceNameFromSelfLink(disk.Region) } else { - diskZone, _ := disk["zone"].(string) locationType = "zones" - location = tpgresource.GetResourceNameFromSelfLink(diskZone) + location = tpgresource.GetResourceNameFromSelfLink(disk.Zone) } - diskName, _ := disk["name"].(string) - urlBase := fmt.Sprintf("{{"{{"}}ComputeBasePath{{"}}"}}projects/%s/%s/%s/disks/%s", project, locationType, location, diskName) + urlBase := fmt.Sprintf("{{"{{"}}ComputeBasePath{{"}}"}}projects/%s/%s/%s/disks/%s", project, locationType, location, disk.Name) if d.HasChange("boot_disk.0.initialize_params.0.size") { obj["sizeGb"] = d.Get("boot_disk.0.initialize_params.0.size").(int) @@ -3512,7 +3578,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err } if d.HasChange("boot_disk.0.initialize_params.0.labels") { obj["labels"] = tpgresource.ConvertStringMap(d.Get("boot_disk.0.initialize_params.0.labels").(map[string]interface{})) - obj["labelFingerprint"] = disk["labelFingerprint"] + obj["labelFingerprint"] = disk.LabelFingerprint err := updateDisk(d, config, userAgent, project, urlBase+"/setLabels", obj) if err != nil { return err @@ -3528,17 +3594,9 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err // can detach disks, it's possible that there are fewer disks currently attached than there // were at the time we ran terraform plan. currDisks := map[string]struct{}{} - instanceDisksRaw, _ := instance["disks"].([]interface{}) - for _, diskRaw := range instanceDisksRaw { - disk, ok := diskRaw.(map[string]interface{}) - if !ok { - continue - } - isBoot, _ := disk["boot"].(bool) - diskType, _ := disk["type"].(string) - deviceName, _ := disk["deviceName"].(string) - if !isBoot && diskType != "SCRATCH" { - currDisks[deviceName] = struct{}{} + for _, disk := range instance.Disks { + if !disk.Boot && disk.Type != "SCRATCH" { + currDisks[disk.DeviceName] = struct{}{} } } @@ -3548,17 +3606,16 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err oDisks := map[uint64]string{} for _, disk := range o.([]interface{}) { diskConfig := disk.(map[string]interface{}) - computeDisk, err := expandAttachedDisk(diskConfig, d, config) + computeDisk, err := expandAttachedDiskTyped(diskConfig, d, config) if err != nil { return err } - hash, err := hashstructure.Hash(computeDisk, nil) + hash, err := hashstructure.Hash(*computeDisk, nil) if err != nil { return err } - computeDiskDeviceName, _ := computeDisk["deviceName"].(string) - if _, ok := currDisks[computeDiskDeviceName]; ok { - oDisks[hash] = computeDiskDeviceName + if _, ok := currDisks[computeDisk.DeviceName]; ok { + oDisks[hash] = computeDisk.DeviceName } } @@ -3567,21 +3624,26 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err // keep track of the hash of the full disk. // If a disk with a certain hash is only in the new config, it should be attached. nDisks := map[uint64]struct{}{} - var attach []map[string]interface{} + var attach []*compute.AttachedDisk for _, disk := range n.([]interface{}) { diskConfig := disk.(map[string]interface{}) - computeDisk, err := expandAttachedDisk(diskConfig, d, config) + computeDisk, err := expandAttachedDiskTyped(diskConfig, d, config) if err != nil { return err } - hash, err := hashstructure.Hash(computeDisk, nil) + hash, err := hashstructure.Hash(*computeDisk, nil) if err != nil { return err } nDisks[hash] = struct{}{} if _, ok := oDisks[hash]; !ok { - attach = append(attach, computeDisk) + computeDiskV1 := &compute.AttachedDisk{} + err = tpgresource.Convert(computeDisk, computeDiskV1) + if err != nil { + return err + } + attach = append(attach, computeDiskV1) } } @@ -3617,7 +3679,11 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err } // Attach the new disks - for _, diskBody := range attach { + for _, disk := range attach { + diskBody, err := tpgresource.ConvertToMap(disk) + if err != nil { + return fmt.Errorf("Error converting attached disk: %s", err) + } url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/attachDisk") if err != nil { return errwrap.Wrapf("Error generating URL: {{"{{"}}err{{"}}"}}", err) @@ -3638,7 +3704,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err if opErr != nil { return opErr } - log.Printf("[DEBUG] Successfully attached disk %s", diskBody["source"]) + log.Printf("[DEBUG] Successfully attached disk %s", disk.Source) } } @@ -3829,7 +3895,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err // Attributes which can only be changed if the instance is stopped if needToStopInstanceBeforeUpdating { - statusBeforeUpdate, _ := instance["status"].(string) + statusBeforeUpdate := instance.Status desiredStatus := d.Get("desired_status").(string) if statusBeforeUpdate == "RUNNING" && desiredStatus != "TERMINATED" && !d.Get("allow_stopping_for_update").(bool) { @@ -3862,8 +3928,13 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err } if d.HasChange("min_cpu_platform") { - body := map[string]interface{}{ - "minCpuPlatform": d.Get("min_cpu_platform").(string), + minCpuPlatform := d.Get("min_cpu_platform") + req := &compute.InstancesSetMinCpuPlatformRequest{ + MinCpuPlatform: minCpuPlatform.(string), + } + body, err := tpgresource.ConvertToMap(req) + if err != nil { + return fmt.Errorf("Error converting min cpu platform request: %s", err) } url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/setMinCpuPlatform") if err != nil { @@ -3891,8 +3962,12 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err if err != nil { return err } - body := map[string]interface{}{ - "machineType": mt.RelativeLink(), + req := &compute.InstancesSetMachineTypeRequest{ + MachineType: mt.RelativeLink(), + } + body, err := tpgresource.ConvertToMap(req) + if err != nil { + return fmt.Errorf("Error converting machine type request: %s", err) } url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/setMachineType") if err != nil { @@ -3917,11 +3992,15 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err if d.HasChange("service_account.0.email") || scopesChange { sa := d.Get("service_account").([]interface{}) - body := map[string]interface{}{"email": ""} + req := &compute.InstancesSetServiceAccountRequest{ForceSendFields: []string{"email"}} if !isEmptyServiceAccountBlock(d) && len(sa) > 0 && sa[0] != nil { saMap := sa[0].(map[string]interface{}) - body["email"] = saMap["email"].(string) - body["scopes"] = tpgresource.CanonicalizeServiceScopes(tpgresource.ConvertStringSet(saMap["scopes"].(*schema.Set))) + req.Email = saMap["email"].(string) + req.Scopes = tpgresource.CanonicalizeServiceScopes(tpgresource.ConvertStringSet(saMap["scopes"].(*schema.Set))) + } + body, err := tpgresource.ConvertToMap(req) + if err != nil { + return fmt.Errorf("Error converting service account request: %s", err) } url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/setServiceAccount") if err != nil { @@ -3945,8 +4024,13 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err } if d.HasChange("enable_display") { - body := map[string]interface{}{ - "enableDisplay": d.Get("enable_display").(bool), + req := &compute.DisplayDevice{ + EnableDisplay: d.Get("enable_display").(bool), + ForceSendFields: []string{"EnableDisplay"}, + } + body, err := tpgresource.ConvertToMap(req) + if err != nil { + return fmt.Errorf("Error converting display device request: %s", err) } url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/updateDisplayDevice") if err != nil { @@ -4076,7 +4160,9 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err if err != nil { return err } - instance = freshInstMap + if err := tpgresource.Convert(freshInstMap, instance); err != nil { + return fmt.Errorf("Error parsing instance response: %s", err) + } } for _, patch := range updatesToNIWhileStopped { instanceMap, err := tpgresource.ConvertToMap(instance) @@ -4106,7 +4192,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err {{ if ne $.TargetVersionName `ga` -}} // The access config must be updated only if the machine is still RUNNING and after each access_config for each interface has been re-created. - err = computeInstanceAddSecurityPolicy(d, config, securityPolicies, project, zone, userAgent, d.Get("name").(string)) + err = computeInstanceAddSecurityPolicy(d, config, securityPolicies, project, zone, userAgent, instance.Name) if err != nil { return fmt.Errorf("Error updating instance while setting the security policies: %s", err) } @@ -4118,7 +4204,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err if err := tpgresource.SetResourceIdentityAttributes(d, map[string]interface{}{ "project": project, "zone": zone, - "name": instance["name"], + "name": instance.Name, }); err != nil { return err } @@ -4144,32 +4230,23 @@ func startInstanceOperation(d *schema.ResourceData, config *transport_tpg.Config } var encryptedDisks []map[string]interface{} - if disksRaw, ok := instanceFromConfig["disks"].([]interface{}); ok { - for _, raw := range disksRaw { - disk, ok := raw.(map[string]interface{}) - if !ok { - continue - } - dek, ok := disk["diskEncryptionKey"].(map[string]interface{}) - if !ok { - continue - } + for _, disk := range instanceFromConfig.Disks { + if disk.DiskEncryptionKey != nil { diskKey := map[string]interface{}{} - if v, _ := dek["rawKey"].(string); v != "" { - diskKey["rawKey"] = v + if disk.DiskEncryptionKey.RawKey != "" { + diskKey["rawKey"] = disk.DiskEncryptionKey.RawKey } - if v, _ := dek["rsaEncryptedKey"].(string); v != "" { - diskKey["rsaEncryptedKey"] = v + if disk.DiskEncryptionKey.RsaEncryptedKey != "" { + diskKey["rsaEncryptedKey"] = disk.DiskEncryptionKey.RsaEncryptedKey } - if v, _ := dek["kmsKeyName"].(string); v != "" { - diskKey["kmsKeyName"] = v + if disk.DiskEncryptionKey.KmsKeyName != "" { + diskKey["kmsKeyName"] = disk.DiskEncryptionKey.KmsKeyName } - if v, _ := dek["kmsKeyServiceAccount"].(string); v != "" { - diskKey["kmsKeyServiceAccount"] = v + if disk.DiskEncryptionKey.KmsKeyServiceAccount != "" { + diskKey["kmsKeyServiceAccount"] = disk.DiskEncryptionKey.KmsKeyServiceAccount } - source, _ := disk["source"].(string) encryptedDisks = append(encryptedDisks, map[string]interface{}{ - "source": source, + "source": disk.Source, "diskEncryptionKey": diskKey, }) } @@ -4285,6 +4362,21 @@ func expandAttachedDisk(diskConfig map[string]interface{}, d *schema.ResourceDat return disk, nil } +// expandAttachedDiskTyped adapts the map-based expandAttachedDisk output to the +// typed *compute.AttachedDisk still required by callers that build Apiary +// request structs directly or read typed fields. +func expandAttachedDiskTyped(diskConfig map[string]interface{}, d *schema.ResourceData, meta interface{}) (*compute.AttachedDisk, error) { + expanded, err := expandAttachedDisk(diskConfig, d, meta) + if err != nil { + return nil, err + } + disk := &compute.AttachedDisk{} + if err := convertViaJSON(expanded, disk); err != nil { + return nil, fmt.Errorf("Error converting attached disk: %s", err) + } + return disk, nil +} + // See comment on expandInstanceTemplateGuestAccelerators regarding why this // code is duplicated. func expandInstanceGuestAccelerators(d tpgresource.TerraformResourceData, config *transport_tpg.Config) ([]interface{}, error) { @@ -4312,6 +4404,25 @@ func expandInstanceGuestAccelerators(d tpgresource.TerraformResourceData, config return guestAccelerators, nil } +// expandInstanceGuestAcceleratorsTyped adapts the map-based +// expandInstanceGuestAccelerators output to the typed +// []*compute.AcceleratorConfig still required by callers that build Apiary +// request structs directly. +func expandInstanceGuestAcceleratorsTyped(d tpgresource.TerraformResourceData, config *transport_tpg.Config) ([]*compute.AcceleratorConfig, error) { + expanded, err := expandInstanceGuestAccelerators(d, config) + if err != nil { + return nil, err + } + if expanded == nil { + return nil, nil + } + accels := make([]*compute.AcceleratorConfig, 0, len(expanded)) + if err := convertViaJSON(expanded, &accels); err != nil { + return nil, fmt.Errorf("Error converting guest accelerators: %s", err) + } + return accels, nil +} + // suppressEmptyGuestAcceleratorDiff is used to work around perpetual diff // issues when a count of `0` guest accelerators is desired. This may occur when // guest_accelerator support is controlled via a module variable. E.g.: @@ -4513,6 +4624,21 @@ func expandParams(d *schema.ResourceData) (map[string]interface{}, error) { return params, nil } +// expandParamsTyped adapts the map-based expandParams output to the typed +// *compute.InstanceParams still required by callers that build Apiary request +// structs directly. +func expandParamsTyped(d *schema.ResourceData) (*compute.InstanceParams, error) { + expanded, err := expandParams(d) + if err != nil { + return nil, err + } + params := &compute.InstanceParams{} + if err := convertViaJSON(expanded, params); err != nil { + return nil, fmt.Errorf("Error converting params: %s", err) + } + return params, nil +} + func expandBootDisk(d *schema.ResourceData, config *transport_tpg.Config, project string) (map[string]interface{}, error) { userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) if err != nil { @@ -4699,25 +4825,33 @@ func expandBootDisk(d *schema.ResourceData, config *transport_tpg.Config, projec return disk, nil } -func flattenBootDisk(d *schema.ResourceData, disk map[string]interface{}, config *transport_tpg.Config) []map[string]interface{} { - guestOsFeaturesSlice := make([]interface{}, 0) - if gof, ok := disk["guestOsFeatures"].([]interface{}); ok { - for _, raw := range gof { - f, ok := raw.(map[string]interface{}) - if !ok { - continue - } - if t, _ := f["type"].(string); t != "" { - guestOsFeaturesSlice = append(guestOsFeaturesSlice, map[string]interface{}{"type": t}) - } +// expandBootDiskTyped adapts the map-based expandBootDisk output to the typed +// *compute.AttachedDisk still required by callers that build Apiary request +// structs directly. +func expandBootDiskTyped(d *schema.ResourceData, config *transport_tpg.Config, project string) (*compute.AttachedDisk, error) { + expanded, err := expandBootDisk(d, config, project) + if err != nil { + return nil, err + } + disk := &compute.AttachedDisk{} + if err := convertViaJSON(expanded, disk); err != nil { + return nil, fmt.Errorf("Error converting boot disk: %s", err) + } + return disk, nil +} + +func flattenBootDisk(d *schema.ResourceData, disk *compute.AttachedDisk, config *transport_tpg.Config) []map[string]interface{} { + guestOsFeaturesSlice := make([]interface{}, 0, len(disk.GuestOsFeatures)) + for _, f := range disk.GuestOsFeatures { + if f != nil && f.Type != "" { + guestOsFeaturesSlice = append(guestOsFeaturesSlice, map[string]interface{}{"type": f.Type}) } } - diskSource, _ := disk["source"].(string) result := map[string]interface{}{ - "auto_delete": disk["autoDelete"], - "device_name": disk["deviceName"], - "mode": disk["mode"], - "source": tpgresource.ConvertSelfLinkToV1(diskSource), + "auto_delete": disk.AutoDelete, + "device_name": disk.DeviceName, + "mode": disk.Mode, + "source": tpgresource.ConvertSelfLinkToV1(disk.Source), "guest_os_features": flattenComputeInstanceGuestOsFeatures(guestOsFeaturesSlice), "force_attach": d.Get("boot_disk.0.force_attach"), // disk_encryption_key_raw is not returned from the API, so copy it from what the user @@ -4726,20 +4860,18 @@ func flattenBootDisk(d *schema.ResourceData, disk map[string]interface{}, config "disk_encryption_key_rsa": d.Get("boot_disk.0.disk_encryption_key_rsa"), } if _,ok := d.GetOk("boot_disk.0.interface"); ok { - result["interface"] = disk["interface"] + result["interface"] = disk.Interface } if v, ok := d.GetOk("boot_disk.0.force_attach"); ok { result["force_attach"] = v.(bool) } - diskDetails, err := getDisk(diskSource, d, config) + diskDetails, err := getDisk(disk.Source, d, config) // Resource policies can get autofilled from the API and on this field and this will cause the instance to recreate // This overrides any value set by the API not to cause a diff when the user didn't set this in their config. - if diskDetails != nil { - if d.Get("boot_disk.0.initialize_params.0.resource_policies.0") == nil || d.Get("boot_disk.0.initialize_params.0.resource_policies") == nil { - delete(diskDetails, "resourcePolicies") - } + if d.Get("boot_disk.0.initialize_params.0.resource_policies.0") == nil || d.Get("boot_disk.0.initialize_params.0.resource_policies") == nil { + diskDetails.ResourcePolicies = nil } if err != nil { @@ -4752,45 +4884,35 @@ func flattenBootDisk(d *schema.ResourceData, disk map[string]interface{}, config result["initialize_params"] = m } } else { - diskType, _ := diskDetails["type"].(string) - storagePool, _ := diskDetails["storagePool"].(string) - var replicaZones []string - if rz, ok := diskDetails["replicaZones"].([]interface{}); ok { - for _, z := range rz { - if s, ok := z.(string); ok { - replicaZones = append(replicaZones, s) - } - } - } result["initialize_params"] = []map[string]interface{}{{"{{"}} - "type": tpgresource.GetResourceNameFromSelfLink(diskType), + "type": tpgresource.GetResourceNameFromSelfLink(diskDetails.Type), // If the config specifies a family name that doesn't match the image name, then // the diff won't be properly suppressed. See DiffSuppressFunc for this field. - "image": diskDetails["sourceImage"], + "image": diskDetails.SourceImage, "source_image_encryption_key": d.Get("boot_disk.0.initialize_params.0.source_image_encryption_key"), "snapshot": d.Get("boot_disk.0.initialize_params.0.snapshot"), "source_snapshot_encryption_key": d.Get("boot_disk.0.initialize_params.0.source_snapshot_encryption_key"), - "architecture": diskDetails["architecture"], - "size": getInt(diskDetails["sizeGb"]), - "labels": diskDetails["labels"], + "architecture": diskDetails.Architecture, + "size": diskDetails.SizeGb, + "labels": diskDetails.Labels, "resource_manager_tags": d.Get("boot_disk.0.initialize_params.0.resource_manager_tags"), - "resource_policies": diskDetails["resourcePolicies"], - "provisioned_iops": getInt(diskDetails["provisionedIops"]), - "provisioned_throughput": getInt(diskDetails["provisionedThroughput"]), - "enable_confidential_compute": diskDetails["enableConfidentialCompute"], - "storage_pool": tpgresource.GetResourceNameFromSelfLink(storagePool), - "replica_zones": flattenReplicaZones(replicaZones), + "resource_policies": diskDetails.ResourcePolicies, + "provisioned_iops": diskDetails.ProvisionedIops, + "provisioned_throughput": diskDetails.ProvisionedThroughput, + "enable_confidential_compute": diskDetails.EnableConfidentialCompute, + "storage_pool": tpgresource.GetResourceNameFromSelfLink(diskDetails.StoragePool), + "replica_zones": flattenReplicaZones(diskDetails.ReplicaZones), {{"}}"}} } - if key, ok := disk["diskEncryptionKey"].(map[string]interface{}); ok { - if sha256, _ := key["sha256"].(string); sha256 != "" { - result["disk_encryption_key_sha256"] = sha256 + if disk.DiskEncryptionKey != nil { + if disk.DiskEncryptionKey.Sha256 != "" { + result["disk_encryption_key_sha256"] = disk.DiskEncryptionKey.Sha256 } - if kmsKeyName, _ := key["kmsKeyName"].(string); kmsKeyName != "" { + if disk.DiskEncryptionKey.KmsKeyName != "" { // The response for crypto keys often includes the version of the key which needs to be removed // format: projects//locations//keyRings//cryptoKeys//cryptoKeyVersions/1 - result["kms_key_self_link"] = strings.Split(kmsKeyName, "/cryptoKeyVersions")[0] + result["kms_key_self_link"] = strings.Split(disk.DiskEncryptionKey.KmsKeyName, "/cryptoKeyVersions")[0] } if v, ok := d.GetOk("boot_disk.0.disk_encryption_service_account"); ok { result["disk_encryption_service_account"] = v.(string) @@ -4809,29 +4931,41 @@ func expandScratchDisks(d *schema.ResourceData, config *transport_tpg.Config, pr n := d.Get("scratch_disk.#").(int) scratchDisks := make([]interface{}, 0, n) for i := 0; i < n; i++ { - scratchDisk := map[string]interface{}{ + scratchDisks = append(scratchDisks, map[string]interface{}{ "autoDelete": true, "type": "SCRATCH", "deviceName": d.Get(fmt.Sprintf("scratch_disk.%d.device_name", i)).(string), "interface": d.Get(fmt.Sprintf("scratch_disk.%d.interface", i)).(string), + "diskSizeGb": strconv.Itoa(d.Get(fmt.Sprintf("scratch_disk.%d.size", i)).(int)), "initializeParams": map[string]interface{}{ "diskType": diskType.RelativeLink(), }, - } - if size := d.Get(fmt.Sprintf("scratch_disk.%d.size", i)).(int); size > 0 { - scratchDisk["diskSizeGb"] = strconv.Itoa(size) - } - scratchDisks = append(scratchDisks, scratchDisk) + }) } return scratchDisks, nil } -func flattenScratchDisk(disk map[string]interface{}) map[string]interface{} { +// expandScratchDisksTyped adapts the map-based expandScratchDisks output to the +// typed []*compute.AttachedDisk still required by callers that build Apiary +// request structs directly. +func expandScratchDisksTyped(d *schema.ResourceData, config *transport_tpg.Config, project string) ([]*compute.AttachedDisk, error) { + expanded, err := expandScratchDisks(d, config, project) + if err != nil { + return nil, err + } + disks := make([]*compute.AttachedDisk, 0, len(expanded)) + if err := convertViaJSON(expanded, &disks); err != nil { + return nil, fmt.Errorf("Error converting scratch disks: %s", err) + } + return disks, nil +} + +func flattenScratchDisk(disk *compute.AttachedDisk) map[string]interface{} { result := map[string]interface{}{ - "device_name": disk["deviceName"], - "interface": disk["interface"], - "size": getInt(disk["diskSizeGb"]), + "device_name": disk.DeviceName, + "interface": disk.Interface, + "size": disk.DiskSizeGb, } return result } @@ -4967,30 +5101,20 @@ func CheckForCommonAliasIp(old, new map[string]interface{}) []interface{} { } {{ if ne $.TargetVersionName `ga` -}} -func CheckForCommonAliasIpv6(old, new map[string]interface{}) []interface{} { +func CheckForCommonAliasIpv6(old, new *compute.NetworkInterface) []*compute.AliasIpRange { + return checkForCommonAliasIpRanges(old.AliasIpv6Ranges, new.AliasIpv6Ranges) +} + +func checkForCommonAliasIpRanges(old, new []*compute.AliasIpRange) []*compute.AliasIpRange { newAliasIpMap := make(map[string]bool) - if newRanges, ok := new["aliasIpv6Ranges"].([]interface{}); ok { - for _, raw := range newRanges { - ipRange, ok := raw.(map[string]interface{}) - if !ok { - continue - } - cidr, _ := ipRange["ipCidrRange"].(string) - newAliasIpMap[cidr] = true - } + for _, ipRange := range new { + newAliasIpMap[ipRange.IpCidrRange] = true } - resultAliasIpRanges := make([]interface{}, 0) - if oldRanges, ok := old["aliasIpv6Ranges"].([]interface{}); ok { - for _, raw := range oldRanges { - ipRange, ok := raw.(map[string]interface{}) - if !ok { - continue - } - cidr, _ := ipRange["ipCidrRange"].(string) - if newAliasIpMap[cidr] { - resultAliasIpRanges = append(resultAliasIpRanges, ipRange) - } + resultAliasIpRanges := make([]*compute.AliasIpRange, 0) + for _, val := range old { + if newAliasIpMap[val.IpCidrRange] { + resultAliasIpRanges = append(resultAliasIpRanges, val) } } return resultAliasIpRanges diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl index 9e05946b84e3..a26a7149852b 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl @@ -2,6 +2,7 @@ package compute {{- if ne $.TargetVersionName "ga" }} import ( + "encoding/json" "fmt" "log" "strings" @@ -140,7 +141,30 @@ func resourceComputeInstanceFromMachineImageCreate(d *schema.ResourceData, meta return err } - instanceBody := instance + // Force send all top-level fields that have been set in case they're overridden to zero values. + // Initialize ForceSendFields to empty so we don't get things that the instance resource + // always force-sends. + instance.ForceSendFields = []string{} + for f, s := range computeInstanceFromMachineImageSchema() { + // It seems that GetOkExists always returns true for sets. + // TODO: confirm this and file issue against Terraform core. + // In the meantime, don't force send sets. + if s.Type == schema.TypeSet { + continue + } + + if _, exists := d.GetOkExists(f); exists { + // Assume for now that all fields are exact snake_case versions of the API fields. + // This won't necessarily always be true, but it serves as a good approximation and + // can be adjusted later as we discover issues. + instance.ForceSendFields = append(instance.ForceSendFields, tpgresource.SnakeToPascalCase(f)) + } + } + + instanceBody, err := tpgresource.ConvertToMap(instance) + if err != nil { + return fmt.Errorf("Error converting instance: %s", err) + } if paramsBody, err := expandParams(d); err != nil { return fmt.Errorf("Error creating params: %s", err) @@ -220,7 +244,17 @@ func resourceComputeInstanceFromMachineImageCreate(d *schema.ResourceData, meta return fmt.Errorf("Error expanding scheduling: %s", err) } if schedulingBody != nil { - instanceBody["scheduling"] = schedulingOmitEmpty(schedulingBody) + // Strip nil Duration fields so the source machine image values are inherited + // instead of being overridden with null. Use json.Marshal to detect typed nils + // (map[string]interface{}(nil)) that are not equal to untyped nil. + for _, k := range []string{"localSsdRecoveryTimeout", "maxRunDuration", "gracefulShutdown", "preemptionNoticeDuration"} { + if v, ok := schedulingBody[k]; ok { + if b, err := json.Marshal(v); err == nil && string(b) == "null" { + delete(schedulingBody, k) + } + } + } + instanceBody["scheduling"] = schedulingBody } } else if srcProps, ok := miRes["sourceInstanceProperties"].(map[string]interface{}); ok { if schedulingRaw, ok := srcProps["scheduling"].(map[string]interface{}); ok { @@ -254,35 +288,6 @@ func resourceComputeInstanceFromMachineImageCreate(d *schema.ResourceData, meta instanceBody["advancedMachineFeatures"] = amf } - delete(instanceBody, "canIpForward") - delete(instanceBody, "deletionProtection") - forceableScalars := map[string]string{ - "can_ip_forward": "canIpForward", - "deletion_protection": "deletionProtection", - "description": "description", - "hostname": "hostname", - "machine_type": "machineType", - "min_cpu_platform": "minCpuPlatform", - "name": "name", - "key_revocation_action_type": "keyRevocationActionType", -{{- if ne $.TargetVersionName "ga" }} - "erase_windows_vss_signature": "eraseWindowsVssSignature", -{{- end }} - } - for field, key := range forceableScalars { - if _, exists := d.GetOkExists(field); !exists { - continue - } - if _, present := instanceBody[key]; !present { - instanceBody[key] = d.Get(field) - } - } - if _, exists := d.GetOkExists("zone"); exists { - if _, present := instanceBody["zone"]; !present { - instanceBody["zone"] = "" - } - } - log.Printf("[INFO] Requesting instance creation") insertURL := fmt.Sprintf("%sprojects/%s/zones/%s/instances", transport_tpg.BaseUrl(Product, config), project, z) res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ @@ -298,7 +303,7 @@ func resourceComputeInstanceFromMachineImageCreate(d *schema.ResourceData, meta } // Store the ID now - d.SetId(fmt.Sprintf("projects/%s/zones/%s/instances/%s", project, z, d.Get("name").(string))) + d.SetId(fmt.Sprintf("projects/%s/zones/%s/instances/%s", project, z, instance.Name)) // Wait for the operation to complete err = ComputeOperationWaitTime(config, res, project, @@ -325,7 +330,7 @@ func adjustInstanceFromMachineImageDisks(d *schema.ResourceData, config *transpo } if _, hasBootDisk := d.GetOk("boot_disk"); hasBootDisk { - bootDisk, err := expandBootDisk(d, config, project) + bootDisk, err := expandBootDiskTyped(d, config, project) if err != nil { return nil, err } @@ -353,7 +358,7 @@ func adjustInstanceFromMachineImageDisks(d *schema.ResourceData, config *transpo } if _, hasScratchDisk := d.GetOk("scratch_disk"); hasScratchDisk { - scratchDisks, err := expandScratchDisks(d, config, project) + scratchDisks, err := expandScratchDisksTyped(d, config, project) if err != nil { return nil, err } @@ -385,7 +390,7 @@ func adjustInstanceFromMachineImageDisks(d *schema.ResourceData, config *transpo if attachedDisksCount > 0 { for i := 0; i < attachedDisksCount; i++ { diskConfig := d.Get(fmt.Sprintf("attached_disk.%d", i)).(map[string]interface{}) - disk, err := expandAttachedDisk(diskConfig, d, config) + disk, err := expandAttachedDiskTyped(diskConfig, d, config) if err != nil { return nil, err } diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl index 7e73d1afeba2..dac863ead702 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl @@ -1,6 +1,7 @@ package compute import ( + "encoding/json" "fmt" "log" "regexp" @@ -12,6 +13,12 @@ import ( "github.com/hashicorp/terraform-provider-google/google/registry" "github.com/hashicorp/terraform-provider-google/google/tpgresource" transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" + +{{ if eq $.TargetVersionName `ga` }} + "google.golang.org/api/compute/v1" +{{- else }} + compute "google.golang.org/api/compute/v0.beta" +{{- end }} ) func ResourceComputeInstanceFromTemplate() *schema.Resource { @@ -103,21 +110,10 @@ func resourceComputeInstanceFromTemplateCreate(d *schema.ResourceData, meta inte return err } log.Printf("[DEBUG] Loading zone: %s", z) - zoneUrl, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}") - if err != nil { - return err - } - zone, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "GET", - Project: project, - RawURL: zoneUrl, - UserAgent: userAgent, - }) + zone, err := NewClient(config, userAgent).Zones.Get(project, z).Do() if err != nil { return fmt.Errorf("Error loading zone '%s': %s", z, err) } - zoneRegion, _ := zone["region"].(string) instance, err := expandComputeInstance(project, d, config) if err != nil { @@ -129,7 +125,13 @@ func resourceComputeInstanceFromTemplateCreate(d *schema.ResourceData, meta inte return fmt.Errorf("Error creating metadata: %s", err) } if len(metadataMap) > 0 { - instance["metadata"] = metadataMap + metadataBytes, err := json.Marshal(metadataMap) + if err != nil { + return fmt.Errorf("Error marshaling metadata: %s", err) + } + if err := json.Unmarshal(metadataBytes, &instance.Metadata); err != nil { + return fmt.Errorf("Error setting metadata: %s", err) + } } {{- if ne $.TargetVersionName "ga" }} partnerMetadataMap, err := resourceInstancePartnerMetadata(d) @@ -141,8 +143,12 @@ func resourceComputeInstanceFromTemplateCreate(d *schema.ResourceData, meta inte if err != nil { return fmt.Errorf("Error converting partner metadata: %s", err) } - if len(partnerMetadataConverted) > 0 { - instance["partnerMetadata"] = partnerMetadataConverted + partnerMetadataBytes, err := json.Marshal(partnerMetadataConverted) + if err != nil { + return fmt.Errorf("Error marshaling partner metadata: %s", err) + } + if err := json.Unmarshal(partnerMetadataBytes, &instance.PartnerMetadata); err != nil { + return fmt.Errorf("Error setting partner metadata: %s", err) } } {{- end }} @@ -153,12 +159,19 @@ func resourceComputeInstanceFromTemplateCreate(d *schema.ResourceData, meta inte return err } + it := compute.InstanceTemplate{} var relativeUrl string - var templateUrl string - isFromRegionalTemplate := !strings.Contains(sourceInstanceTemplate, "global/instanceTemplates") - if !isFromRegionalTemplate { + + if strings.Contains(sourceInstanceTemplate, "global/instanceTemplates") { + instanceTemplate, err := NewClient(config, userAgent).InstanceTemplates.Get(project, tpl.Name).Do() + if err != nil { + return err + } + + it = *instanceTemplate relativeUrl = tpl.RelativeLink() - templateUrl, err = tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/global/instanceTemplates/" + tpl.Name) + + instance.Disks, err = adjustInstanceFromTemplateDisks(d, config, &it, zone, project, false) if err != nil { return err } @@ -168,37 +181,45 @@ func resourceComputeInstanceFromTemplateCreate(d *schema.ResourceData, meta inte return err } - templateUrl, err = tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/regions/{{"{{"}}region{{"}}"}}/instanceTemplates/" + tpl.Name) + url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/regions/{{"{{"}}region{{"}}"}}/instanceTemplates/" + tpl.Name) if err != nil { return err } - } - it, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "GET", - Project: project, - RawURL: templateUrl, - UserAgent: userAgent, - }) - if err != nil { - return err - } + instanceTemplate, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: project, + RawURL: url, + UserAgent: userAgent, + }) + if err != nil { + return err + } - instance["disks"], err = adjustInstanceFromTemplateDisks(d, config, it, z, zoneRegion, project, isFromRegionalTemplate) - if err != nil { - return err + instancePropertiesObj, err := json.Marshal(instanceTemplate) + if err != nil { + fmt.Println(err) + return err + } + + if err := json.Unmarshal(instancePropertiesObj, &it); err != nil { + fmt.Println(err) + return err + } + + instance.Disks, err = adjustInstanceFromTemplateDisks(d, config, &it, zone, project, true) + if err != nil { + return err + } } // expandComputeInstance no longer sets Scheduling; handle it here so the user's // scheduling overrides are applied. When no scheduling block is set, inherit from - // the instance template. When set, use the map-based result from expandScheduling. + // the instance template. When set, convert the map-based result from expandScheduling + // into the typed struct required by the API client. if _, hasSchedule := d.GetOk("scheduling"); !hasSchedule { - if props, ok := it["properties"].(map[string]interface{}); ok { - if sched, ok := props["scheduling"]; ok { - instance["scheduling"] = sched - } - } + instance.Scheduling = it.Properties.Scheduling } else { schedulingMap, err := expandScheduling(d.Get("scheduling")) if err != nil { @@ -218,7 +239,15 @@ func resourceComputeInstanceFromTemplateCreate(d *schema.ResourceData, meta inte } } } - instance["scheduling"] = schedulingOmitEmpty(schedulingMap) + schedulingBytes, err := json.Marshal(schedulingMap) + if err != nil { + return fmt.Errorf("Error marshaling scheduling: %s", err) + } + schedulingTyped := &compute.Scheduling{} + if err := json.Unmarshal(schedulingBytes, schedulingTyped); err != nil { + return fmt.Errorf("Error setting scheduling: %s", err) + } + instance.Scheduling = schedulingTyped } {{- if ne $.TargetVersionName "ga" }} @@ -232,66 +261,48 @@ func resourceComputeInstanceFromTemplateCreate(d *schema.ResourceData, meta inte return fmt.Errorf("Error converting partner_metadata: %s", err) } if len(partnerMetadataConverted) > 0 { - instance["partnerMetadata"] = partnerMetadataConverted + partnerMetadataBytes, err := json.Marshal(partnerMetadataConverted) + if err != nil { + return fmt.Errorf("Error marshaling partner_metadata: %s", err) + } + if err := json.Unmarshal(partnerMetadataBytes, &instance.PartnerMetadata); err != nil { + return fmt.Errorf("Error setting partner_metadata: %s", err) + } } } {{- end }} - delete(instance, "canIpForward") - delete(instance, "deletionProtection") - forceableScalars := map[string]string{ - "can_ip_forward": "canIpForward", - "deletion_protection": "deletionProtection", - "description": "description", - "hostname": "hostname", - "machine_type": "machineType", - "min_cpu_platform": "minCpuPlatform", - "name": "name", - "key_revocation_action_type": "keyRevocationActionType", -{{- if ne $.TargetVersionName "ga" }} - "erase_windows_vss_signature": "eraseWindowsVssSignature", -{{- end }} - } - for field, key := range forceableScalars { - if _, exists := d.GetOkExists(field); !exists { + // Force send all top-level fields that have been set in case they're overridden to zero values. + // Initialize ForceSendFields to empty so we don't get things that the instance resource + // always force-sends. + instance.ForceSendFields = []string{} + for f, s := range computeInstanceFromTemplateSchema() { + // It seems that GetOkExists always returns true for sets. + // TODO: confirm this and file issue against Terraform core. + // In the meantime, don't force send sets. + if s.Type == schema.TypeSet { continue } - if _, present := instance[key]; !present { - instance[key] = d.Get(field) - } - } - if _, exists := d.GetOkExists("zone"); exists { - if _, present := instance["zone"]; !present { - instance["zone"] = "" + + if _, exists := d.GetOkExists(f); exists { + // Assume for now that all fields are exact snake_case versions of the API fields. + // This won't necessarily always be true, but it serves as a good approximation and + // can be adjusted later as we discover issues. + instance.ForceSendFields = append(instance.ForceSendFields, tpgresource.SnakeToPascalCase(f)) } } log.Printf("[INFO] Requesting instance creation") - insertUrl, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances") - if err != nil { - return fmt.Errorf("Error generating URL: %s", err) - } - insertUrl, err = transport_tpg.AddQueryParams(insertUrl, map[string]string{"sourceInstanceTemplate": relativeUrl}) - if err != nil { - return err - } - res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "POST", - Project: project, - RawURL: insertUrl, - UserAgent: userAgent, - Body: instance, - }) + op, err := NewClient(config, userAgent).Instances.Insert(project, zone.Name, instance).SourceInstanceTemplate(relativeUrl).Do() if err != nil { return fmt.Errorf("Error creating instance: %s", err) } // Store the ID now - d.SetId(fmt.Sprintf("projects/%s/zones/%s/instances/%s", project, z, d.Get("name").(string))) + d.SetId(fmt.Sprintf("projects/%s/zones/%s/instances/%s", project, z, instance.Name)) // Wait for the operation to complete - waitErr := ComputeOperationWaitTime(config, res, project, + waitErr := ComputeOperationWaitTime(config, op, project, "instance to create", userAgent, d.Timeout(schema.TimeoutCreate)) if waitErr != nil { // The resource didn't actually create @@ -304,50 +315,37 @@ func resourceComputeInstanceFromTemplateCreate(d *schema.ResourceData, meta inte // Instances have disks spread across multiple schema properties. This function // ensures that overriding one of these properties does not override the others. -func adjustInstanceFromTemplateDisks(d *schema.ResourceData, config *transport_tpg.Config, it map[string]interface{}, zoneName, zoneRegion, project string, isFromRegionalTemplate bool) ([]interface{}, error) { - disks := []interface{}{} +func adjustInstanceFromTemplateDisks(d *schema.ResourceData, config *transport_tpg.Config, it *compute.InstanceTemplate, zone *compute.Zone, project string, isFromRegionalTemplate bool) ([]*compute.AttachedDisk, error) { + disks := []*compute.AttachedDisk{} re := regexp.MustCompile(`projects/[^/]+/regions/[^/]+/disks/[^/]+$`) - - var templateDisks []interface{} - if props, ok := it["properties"].(map[string]interface{}); ok { - if dl, ok := props["disks"].([]interface{}); ok { - templateDisks = dl - } - } - if _, hasBootDisk := d.GetOk("boot_disk"); hasBootDisk { - bootDisk, err := expandBootDisk(d, config, project) + bootDisk, err := expandBootDiskTyped(d, config, project) if err != nil { return nil, err } disks = append(disks, bootDisk) } else { // boot disk was not overridden, so use the one from the instance template - for _, rawDisk := range templateDisks { - disk, ok := rawDisk.(map[string]interface{}) - if !ok { - continue - } - if isBoot, _ := disk["boot"].(bool); isBoot { - if source, _ := disk["source"].(string); source != "" && !isFromRegionalTemplate && !re.MatchString(source) { + for _, disk := range it.Properties.Disks { + if disk.Boot { + if disk.Source != "" && !isFromRegionalTemplate && !re.MatchString(disk.Source){ // Instances need a URL for the disk, but instance templates only have the name - disk["source"] = fmt.Sprintf("projects/%s/zones/%s/disks/%s", project, zoneName, source) + disk.Source = fmt.Sprintf("projects/%s/zones/%s/disks/%s", project, zone.Name, disk.Source) } - if ip, ok := disk["initializeParams"].(map[string]interface{}); ok { - if dt, _ := ip["diskType"].(string); dt != "" { + if disk.InitializeParams != nil { + if dt := disk.InitializeParams.DiskType; dt != "" { // Instances need a URL for the disk type, but instance templates // only have the name (since they're global). - ip["diskType"] = fmt.Sprintf("zones/%s/diskTypes/%s", zoneName, dt) + disk.InitializeParams.DiskType = fmt.Sprintf("zones/%s/diskTypes/%s", zone.Name, dt) } - if rp, ok := ip["resourcePolicies"].([]interface{}); ok && len(rp) > 0 { + if rp := disk.InitializeParams.ResourcePolicies; len(rp) > 0 { // Instances need a URL for the resource policy, but instance templates // only have the name (since they're global). for i := range rp { - name, _ := rp[i].(string) - name, _ = parseUniqueId(name) // in some cases the API translation doesn't work and returns entire url when only name is provided. And allows for id to be passed as well - rp[i] = fmt.Sprintf("projects/%s/regions/%s/resourcePolicies/%s", project, regionFromUrl(zoneRegion), name) + rp[i], _ = parseUniqueId(rp[i]) // in some cases the API translation doesn't work and returns entire url when only name is provided. And allows for id to be passed as well + rp[i] = fmt.Sprintf("projects/%s/regions/%s/resourcePolicies/%s", project, regionFromUrl(zone.Region), rp[i]) } - ip["resourcePolicies"] = rp + disk.InitializeParams.ResourcePolicies = rp } } disks = append(disks, disk) @@ -357,24 +355,20 @@ func adjustInstanceFromTemplateDisks(d *schema.ResourceData, config *transport_t } if _, hasScratchDisk := d.GetOk("scratch_disk"); hasScratchDisk { - scratchDisks, err := expandScratchDisks(d, config, project) + scratchDisks, err := expandScratchDisksTyped(d, config, project) if err != nil { return nil, err } disks = append(disks, scratchDisks...) } else { // scratch disks were not overridden, so use the ones from the instance template - for _, rawDisk := range templateDisks { - disk, ok := rawDisk.(map[string]interface{}) - if !ok { - continue - } - if dtype, _ := disk["type"].(string); dtype == "SCRATCH" { - if ip, ok := disk["initializeParams"].(map[string]interface{}); ok { - if dt, _ := ip["diskType"].(string); dt != "" { + for _, disk := range it.Properties.Disks { + if disk.Type == "SCRATCH" { + if disk.InitializeParams != nil { + if dt := disk.InitializeParams.DiskType; dt != "" { // Instances need a URL for the disk type, but instance templates // only have the name (since they're global). - ip["diskType"] = fmt.Sprintf("zones/%s/diskTypes/%s", zoneName, dt) + disk.InitializeParams.DiskType = fmt.Sprintf("zones/%s/diskTypes/%s", zone.Name, dt) } } disks = append(disks, disk) @@ -386,7 +380,7 @@ func adjustInstanceFromTemplateDisks(d *schema.ResourceData, config *transport_t if attachedDisksCount > 0 { for i := 0; i < attachedDisksCount; i++ { diskConfig := d.Get(fmt.Sprintf("attached_disk.%d", i)).(map[string]interface{}) - disk, err := expandAttachedDisk(diskConfig, d, config) + disk, err := expandAttachedDiskTyped(diskConfig, d, config) if err != nil { return nil, err } @@ -395,24 +389,18 @@ func adjustInstanceFromTemplateDisks(d *schema.ResourceData, config *transport_t } } else { // attached disks were not overridden, so use the ones from the instance template - for _, rawDisk := range templateDisks { - disk, ok := rawDisk.(map[string]interface{}) - if !ok { - continue - } - isBoot, _ := disk["boot"].(bool) - dtype, _ := disk["type"].(string) - if !isBoot && dtype != "SCRATCH" { - if source, _ := disk["source"].(string); source != "" && !isFromRegionalTemplate && !re.MatchString(source) { + for _, disk := range it.Properties.Disks { + if !disk.Boot && disk.Type != "SCRATCH" { + if s := disk.Source; s != "" && !isFromRegionalTemplate && !re.MatchString(disk.Source){ // Instances need a URL for the disk source, but instance templates // only have the name (since they're global). - disk["source"] = fmt.Sprintf("zones/%s/disks/%s", zoneName, source) + disk.Source = fmt.Sprintf("zones/%s/disks/%s", zone.Name, s) } - if ip, ok := disk["initializeParams"].(map[string]interface{}); ok { - if dt, _ := ip["diskType"].(string); dt != "" { + if disk.InitializeParams != nil { + if dt := disk.InitializeParams.DiskType; dt != "" { // Instances need a URL for the disk type, but instance templates // only have the name (since they're global). - ip["diskType"] = fmt.Sprintf("zones/%s/diskTypes/%s", zoneName, dt) + disk.InitializeParams.DiskType = fmt.Sprintf("zones/%s/diskTypes/%s", zone.Name, dt) } } disks = append(disks, disk) diff --git a/mmv1/third_party/terraform/services/container/node_config.go.tmpl b/mmv1/third_party/terraform/services/container/node_config.go.tmpl index 1aeeb7bcac0f..4d4b00f7cf4d 100644 --- a/mmv1/third_party/terraform/services/container/node_config.go.tmpl +++ b/mmv1/third_party/terraform/services/container/node_config.go.tmpl @@ -1391,6 +1391,37 @@ func schemaNodeConfig() *schema.Schema { Description: `.`, ValidateFunc: validation.StringInSlice([]string{"MAINTENANCE_INTERVAL_UNSPECIFIED", "AS_NEEDED", "PERIODIC"}, false), }, + {{ if ne $.TargetVersionName `ga` -}} + "opportunistic_maintenance_strategy": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Description: `Strategy that will trigger maintenance on behalf of the customer.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "node_idle_time_window": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The amount of time that a node can remain idle (no customer owned workloads running), before triggering maintenance.`, + }, + "maintenance_availability_window": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The window of time that opportunistic maintenance can run. Example: A setting of 14 days implies that opportunistic maintenance can only be ran in the 2 weeks leading up to the scheduled maintenance date. Setting 28 days allows opportunistic maintenance to run at any time in the scheduled maintenance window (all PERIODIC maintenance is set 28 days in advance).`, + }, + "min_nodes_per_pool": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: `The minimum nodes required to be available in a pool. Blocks maintenance if it would cause the number of running nodes to dip below this value.`, + }, + }, + }, + }, + {{- end }} }, }, }, @@ -2943,9 +2974,37 @@ func expandHostMaintenancePolicy(v interface{}) *container.HostMaintenancePolicy if maintenanceInterval, ok := cfg["maintenance_interval"]; ok { mPolicy.MaintenanceInterval = maintenanceInterval.(string) } + if v, ok := cfg["opportunistic_maintenance_strategy"]; ok { + mPolicy.OpportunisticMaintenanceStrategy = expandOpportunisticMaintenanceStrategy(v) + } return mPolicy } + +func expandOpportunisticMaintenanceStrategy(v interface{}) *container.OpportunisticMaintenanceStrategy { + if v == nil { + return nil + } + ls := v.([]interface{}) + if len(ls) == 0 || ls[0] == nil { + return nil + } + cfg := ls[0].(map[string]interface{}) + strategy := &container.OpportunisticMaintenanceStrategy{ + ForceSendFields: []string{"MinNodesPerPool"}, + } + if nodeIdleTimeWindow, ok := cfg["node_idle_time_window"]; ok { + strategy.NodeIdleTimeWindow = nodeIdleTimeWindow.(string) + } + if maintenanceAvailabilityWindow, ok := cfg["maintenance_availability_window"]; ok { + strategy.MaintenanceAvailabilityWindow = maintenanceAvailabilityWindow.(string) + } + if minNodesPerPool, ok := cfg["min_nodes_per_pool"]; ok { + strategy.MinNodesPerPool = int64(minNodesPerPool.(int)) + } + + return strategy +} {{- end }} func expandConfidentialNodes(configured interface{}) *container.ConfidentialNodes { @@ -3906,7 +3965,21 @@ func flattenHostMaintenancePolicy(c *container.HostMaintenancePolicy) []map[stri result := []map[string]interface{}{} if c != nil { result = append(result, map[string]interface{}{ - "maintenance_interval": c.MaintenanceInterval, + "maintenance_interval": c.MaintenanceInterval, + "opportunistic_maintenance_strategy": flattenOpportunisticMaintenanceStrategy(c.OpportunisticMaintenanceStrategy), + }) + } + + return result +} + +func flattenOpportunisticMaintenanceStrategy(c *container.OpportunisticMaintenanceStrategy) []map[string]interface{} { + result := []map[string]interface{}{} + if c != nil { + result = append(result, map[string]interface{}{ + "node_idle_time_window": c.NodeIdleTimeWindow, + "maintenance_availability_window": c.MaintenanceAvailabilityWindow, + "min_nodes_per_pool": c.MinNodesPerPool, }) } diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl index da6fea09afb8..053226005b7e 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl @@ -347,6 +347,11 @@ fields: api_field: 'nodeConfig.accelerators.acceleratorType' - api_field: 'nodeConfig.gvnic.enabled' - api_field: 'nodeConfig.hostMaintenancePolicy.maintenanceInterval' +{{- if ne $.TargetVersionName "ga" }} + - api_field: 'nodeConfig.hostMaintenancePolicy.opportunisticMaintenanceStrategy.maintenanceAvailabilityWindow' + - api_field: 'nodeConfig.hostMaintenancePolicy.opportunisticMaintenanceStrategy.minNodesPerPool' + - api_field: 'nodeConfig.hostMaintenancePolicy.opportunisticMaintenanceStrategy.nodeIdleTimeWindow' +{{- end }} - api_field: 'nodeConfig.imageType' - api_field: 'nodeConfig.kubeletConfig.allowedUnsafeSysctls' - api_field: 'nodeConfig.kubeletConfig.containerLogMaxFiles' @@ -603,6 +608,14 @@ fields: api_field: 'nodePools.config.gvnic.enabled' - field: 'node_pool.node_config.host_maintenance_policy.maintenance_interval' api_field: 'nodePools.config.hostMaintenancePolicy.maintenanceInterval' +{{- if ne $.TargetVersionName "ga" }} + - field: 'node_pool.node_config.host_maintenance_policy.opportunistic_maintenance_strategy.maintenance_availability_window' + api_field: 'nodePools.config.hostMaintenancePolicy.opportunisticMaintenanceStrategy.maintenanceAvailabilityWindow' + - field: 'node_pool.node_config.host_maintenance_policy.opportunistic_maintenance_strategy.min_nodes_per_pool' + api_field: 'nodePools.config.hostMaintenancePolicy.opportunisticMaintenanceStrategy.minNodesPerPool' + - field: 'node_pool.node_config.host_maintenance_policy.opportunistic_maintenance_strategy.node_idle_time_window' + api_field: 'nodePools.config.hostMaintenancePolicy.opportunisticMaintenanceStrategy.nodeIdleTimeWindow' +{{- end }} - field: 'node_pool.node_config.image_type' api_field: 'nodePools.config.imageType' - field: 'node_pool.node_config.kubelet_config.allowed_unsafe_sysctls' diff --git a/mmv1/third_party/terraform/services/container/resource_container_node_pool_meta.yaml.tmpl b/mmv1/third_party/terraform/services/container/resource_container_node_pool_meta.yaml.tmpl index 6579594519fa..6a7308358e3d 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_node_pool_meta.yaml.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_node_pool_meta.yaml.tmpl @@ -134,6 +134,14 @@ fields: api_field: 'config.gvnic.enabled' - field: 'node_config.host_maintenance_policy.maintenance_interval' api_field: 'config.hostMaintenancePolicy.maintenanceInterval' +{{- if ne $.TargetVersionName "ga" }} + - field: 'node_config.host_maintenance_policy.opportunistic_maintenance_strategy.maintenance_availability_window' + api_field: 'config.hostMaintenancePolicy.opportunisticMaintenanceStrategy.maintenanceAvailabilityWindow' + - field: 'node_config.host_maintenance_policy.opportunistic_maintenance_strategy.min_nodes_per_pool' + api_field: 'config.hostMaintenancePolicy.opportunisticMaintenanceStrategy.minNodesPerPool' + - field: 'node_config.host_maintenance_policy.opportunistic_maintenance_strategy.node_idle_time_window' + api_field: 'config.hostMaintenancePolicy.opportunisticMaintenanceStrategy.nodeIdleTimeWindow' +{{- end }} - field: 'node_config.image_type' api_field: 'config.imageType' - field: 'node_config.kubelet_config.allowed_unsafe_sysctls' diff --git a/mmv1/third_party/terraform/services/container/resource_container_node_pool_test.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_node_pool_test.go.tmpl index 84c5e61d547b..2edb4dd3f4b3 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_node_pool_test.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_node_pool_test.go.tmpl @@ -6017,6 +6017,97 @@ resource "google_container_node_pool" "np" { } `, networkName, subnetworkName, cluster, np) } + +func TestAccContainerNodePool_withOpportunisticMaintenance(t *testing.T) { + t.Parallel() + t.Skip("Opportunistic maintenance strategy is only supported on GPU/TPU node pools; requires GPU capacity/quota and a project allowlisted for GKE host maintenance. Run manually.") + + cluster := fmt.Sprintf("tf-test-cluster-%s", acctest.RandString(t, 10)) + np := fmt.Sprintf("tf-test-np-%s", acctest.RandString(t, 10)) + networkName := fmt.Sprintf("tf-test-net-%s", acctest.RandString(t, 10)) + subnetworkName := fmt.Sprintf("tf-test-sub-%s", acctest.RandString(t, 10)) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckContainerNodePoolDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccContainerNodePool_withOpportunisticMaintenance(cluster, networkName, subnetworkName, np), + }, + { + ResourceName: "google_container_node_pool.np", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccContainerNodePool_withOpportunisticMaintenance(cluster, networkName, subnetworkName, np string) string { + return fmt.Sprintf(` +resource "google_compute_network" "net" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "sub" { + name = "%s" + network = google_compute_network.net.id + ip_cidr_range = "10.0.0.0/20" + region = "us-central1" + secondary_ip_range { + range_name = "pods" + ip_cidr_range = "10.16.0.0/14" + } + secondary_ip_range { + range_name = "services" + ip_cidr_range = "10.20.0.0/20" + } +} + +resource "google_container_cluster" "cluster" { + name = "%s" + location = "us-central1-a" + initial_node_count = 1 + node_config { + machine_type = "n2-standard-2" + } + network = google_compute_network.net.id + subnetwork = google_compute_subnetwork.sub.id + ip_allocation_policy { + cluster_secondary_range_name = "pods" + services_secondary_range_name = "services" + } + deletion_protection = false +} + +resource "google_container_node_pool" "np" { + name = "%s" + location = "us-central1-a" + cluster = google_container_cluster.cluster.name + initial_node_count = 1 + node_config { + machine_type = "n1-standard-2" + guest_accelerator { + type = "nvidia-tesla-t4" + count = 1 + gpu_driver_installation_config { + gpu_driver_version = "DEFAULT" + } + } + host_maintenance_policy { + maintenance_interval = "PERIODIC" + opportunistic_maintenance_strategy { + node_idle_time_window = "600s" + maintenance_availability_window = "1209600s" + min_nodes_per_pool = 1 + } + } + } +} +`, networkName, subnetworkName, cluster, np) +} {{- end }} func TestAccContainerNodePool_withConfidentialBootDisk(t *testing.T) { diff --git a/mmv1/third_party/terraform/services/sql/resource_sql_database_instance.go.tmpl b/mmv1/third_party/terraform/services/sql/resource_sql_database_instance.go.tmpl index a1132f41e8ab..cc045a01b0df 100644 --- a/mmv1/third_party/terraform/services/sql/resource_sql_database_instance.go.tmpl +++ b/mmv1/third_party/terraform/services/sql/resource_sql_database_instance.go.tmpl @@ -605,6 +605,12 @@ API (for read pools, effective_availability_type may differ from availability_ty Computed: true, Description: `Provisioned throughput measured in MiB per second for the data disk. This field is only used for HYPERDISK_BALANCED disk types.`, }, + "replication_lag_max_seconds": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: `The acceptable replication lag, in seconds, after which a read replica recreates itself. The lag must persist for at least five minutes before recreation is triggered. This is a replica level field, and must be between 300 seconds (five minutes) and 31536000 seconds (one year).`, + }, "connection_pool_config": { Type: schema.TypeSet, Optional: true, @@ -2089,6 +2095,7 @@ func expandSqlDatabaseInstanceSettings(configured []interface{}, databaseVersion DataDiskType: _settings["disk_type"].(string), DataDiskProvisionedIops: int64(_settings["data_disk_provisioned_iops"].(int)), DataDiskProvisionedThroughput: int64(_settings["data_disk_provisioned_throughput"].(int)), + ReplicationLagMaxSeconds: int64(_settings["replication_lag_max_seconds"].(int)), PricingPlan: _settings["pricing_plan"].(string), DeletionProtectionEnabled: _settings["deletion_protection_enabled"].(bool), EnableGoogleMlIntegration: _settings["enable_google_ml_integration"].(bool), @@ -3361,6 +3368,7 @@ func flattenSettings(settings *sqladmin.Settings, iType string, d *schema.Resour "disk_size": settings.DataDiskSizeGb, "data_disk_provisioned_iops": settings.DataDiskProvisionedIops, "data_disk_provisioned_throughput": settings.DataDiskProvisionedThroughput, + "replication_lag_max_seconds": settings.ReplicationLagMaxSeconds, "pricing_plan": settings.PricingPlan, "user_labels": settings.UserLabels, "password_validation_policy": settings.PasswordValidationPolicy, diff --git a/mmv1/third_party/terraform/services/sql/resource_sql_database_instance_meta.yaml.tmpl b/mmv1/third_party/terraform/services/sql/resource_sql_database_instance_meta.yaml.tmpl index 9431228b353e..a8892896ae63 100644 --- a/mmv1/third_party/terraform/services/sql/resource_sql_database_instance_meta.yaml.tmpl +++ b/mmv1/third_party/terraform/services/sql/resource_sql_database_instance_meta.yaml.tmpl @@ -142,6 +142,7 @@ fields: - api_field: 'settings.dataDiskProvisionedThroughput' - api_field: 'settings.dataDiskType' field: 'settings.disk_type' + - api_field: 'settings.replicationLagMaxSeconds' - api_field: 'settings.edition' - api_field: 'settings.enableDataplexIntegration' - api_field: 'settings.enableGoogleMlIntegration' diff --git a/mmv1/third_party/terraform/services/sql/resource_sql_database_instance_test.go.tmpl b/mmv1/third_party/terraform/services/sql/resource_sql_database_instance_test.go.tmpl index dd31409ff2a5..7fd9ee656d39 100644 --- a/mmv1/third_party/terraform/services/sql/resource_sql_database_instance_test.go.tmpl +++ b/mmv1/third_party/terraform/services/sql/resource_sql_database_instance_test.go.tmpl @@ -610,6 +610,39 @@ func TestAccSqlDatabaseInstance_settings_basic(t *testing.T) { }) } +func TestAccSqlDatabaseInstance_replicationLagMaxSeconds(t *testing.T) { + t.Parallel() + + instanceName := "tf-test-" + acctest.RandString(t, 10) + replicaName := "tf-test-" + acctest.RandString(t, 10) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccSqlDatabaseInstanceDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testGoogleSqlDatabaseInstanceConfig_replicationLagMaxSeconds(instanceName, replicaName, 600), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_sql_database_instance.replica", "settings.0.replication_lag_max_seconds", "600"), + ), + }, + { + ResourceName: "google_sql_database_instance.replica", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"deletion_protection"}, + }, + { + Config: testGoogleSqlDatabaseInstanceConfig_replicationLagMaxSeconds(instanceName, replicaName, 1200), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_sql_database_instance.replica", "settings.0.replication_lag_max_seconds", "1200"), + ), + }, + }, + }) +} + func TestAccSqlDatabaseInstance_settings_secondary(t *testing.T) { t.Parallel() @@ -6096,6 +6129,40 @@ resource "google_sql_database_instance" "instance" { `, instanceName) } +func testGoogleSqlDatabaseInstanceConfig_replicationLagMaxSeconds(instanceName, replicaName string, lagMaxSeconds int) string { + return fmt.Sprintf(` +resource "google_sql_database_instance" "instance" { + name = "%s" + region = "us-central1" + database_version = "MYSQL_5_7" + deletion_protection = false + + settings { + tier = "db-n1-standard-1" + + backup_configuration { + binary_log_enabled = "true" + enabled = "true" + start_time = "18:00" + } + } +} + +resource "google_sql_database_instance" "replica" { + name = "%s" + region = "us-central1" + database_version = "MYSQL_5_7" + master_instance_name = google_sql_database_instance.instance.name + deletion_protection = false + + settings { + tier = "db-n1-standard-1" + replication_lag_max_seconds = %d + } +} +`, instanceName, replicaName, lagMaxSeconds) +} + func testGoogleSqlDatabaseInstanceConfig_withReplica(instanceName, failoverName string) string { return fmt.Sprintf(` resource "google_sql_database_instance" "instance" { diff --git a/mmv1/third_party/terraform/services/vertexaiaad/vertex_ai_aad_polling.go b/mmv1/third_party/terraform/services/vertexaiaad/vertex_ai_aad_polling.go new file mode 100644 index 000000000000..76e6834065c0 --- /dev/null +++ b/mmv1/third_party/terraform/services/vertexaiaad/vertex_ai_aad_polling.go @@ -0,0 +1,70 @@ +package vertexaiaad + +import ( + "fmt" + "log" + + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +// PollCheckForScopeActive checks the scope's state field during create polling. +// ACTIVE → done, CREATING/UPDATING → retry, FAILED → error. +func PollCheckForScopeActive(resp map[string]interface{}, respErr error) transport_tpg.PollResult { + if respErr != nil { + if transport_tpg.IsGoogleApiErrorWithCode(respErr, 404) { + log.Printf("[DEBUG] AgentAnomalyDetectionScope poll: not found yet, retrying...") + return transport_tpg.PendingStatusPollResult("not found") + } + log.Printf("[DEBUG] AgentAnomalyDetectionScope poll: error: %s", respErr) + return transport_tpg.ErrorPollResult(respErr) + } + + state, ok := resp["state"].(string) + if !ok { + log.Printf("[DEBUG] AgentAnomalyDetectionScope poll: state field not found, retrying...") + return transport_tpg.PendingStatusPollResult("state unknown") + } + + log.Printf("[DEBUG] AgentAnomalyDetectionScope poll: state=%s", state) + + switch state { + case "ACTIVE": + return transport_tpg.SuccessPollResult() + case "CREATING", "UPDATING": + return transport_tpg.PendingStatusPollResult(state) + case "FAILED": + return transport_tpg.ErrorPollResult(fmt.Errorf("AgentAnomalyDetectionScope reached FAILED state")) + default: + return transport_tpg.PendingStatusPollResult(state) + } +} + +// PollCheckForScopeDeleted checks the scope's state field during delete polling. +// 404 → done, DELETING → retry, FAILED → error. +func PollCheckForScopeDeleted(resp map[string]interface{}, respErr error) transport_tpg.PollResult { + if respErr != nil { + if transport_tpg.IsGoogleApiErrorWithCode(respErr, 404) { + log.Printf("[DEBUG] AgentAnomalyDetectionScope delete poll: scope deleted (404)") + return transport_tpg.SuccessPollResult() + } + log.Printf("[DEBUG] AgentAnomalyDetectionScope delete poll: error: %s", respErr) + return transport_tpg.ErrorPollResult(respErr) + } + + state, ok := resp["state"].(string) + if !ok { + log.Printf("[DEBUG] AgentAnomalyDetectionScope delete poll: state field not found, retrying...") + return transport_tpg.PendingStatusPollResult("state unknown") + } + + log.Printf("[DEBUG] AgentAnomalyDetectionScope delete poll: state=%s", state) + + switch state { + case "DELETING": + return transport_tpg.PendingStatusPollResult("DELETING") + case "FAILED": + return transport_tpg.ErrorPollResult(fmt.Errorf("AgentAnomalyDetectionScope deletion reached FAILED state")) + default: + return transport_tpg.PendingStatusPollResult(state) + } +} diff --git a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown index b4173c6b5abc..28aea5da7319 100644 --- a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown @@ -1186,6 +1186,8 @@ gvnic { * `flex_start` - (Optional) Enables Flex Start provisioning model for the node pool. +* `host_maintenance_policy` - (Optional, [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html)) The maintenance policy for the hosts on which the GKE VMs run on. Structure is [documented below](#nested_host_maintenance_policy). + * `local_ssd_count` - (Optional) The amount of local SSD disks that will be attached to each cluster node. Defaults to 0. @@ -1379,6 +1381,20 @@ sole_tenant_config { * `enabled` (Required) - Whether or not the Google Virtual NIC (gVNIC) is enabled +The `host_maintenance_policy` block supports: + +* `maintenance_interval` (Required) - Specifies the frequency of planned maintenance events. Possible values are `MAINTENANCE_INTERVAL_UNSPECIFIED`, `AS_NEEDED`, and `PERIODIC`. + +* `opportunistic_maintenance_strategy` (Optional) - Strategy that will trigger maintenance on behalf of the customer. Structure is [documented below](#nested_opportunistic_maintenance_strategy). + +The `opportunistic_maintenance_strategy` block supports: + +* `node_idle_time_window` (Required) - The amount of time that a node can remain idle (no customer owned workloads running), before triggering maintenance. Format is a duration terminated by `s`, e.g. `"600s"`. + +* `maintenance_availability_window` (Required) - The window of time that opportunistic maintenance can run. Example: A setting of 14 days (`"1209600s"`) implies that opportunistic maintenance can only be ran in the 2 weeks leading up to the scheduled maintenance date. Setting 28 days (`"2419200s"`) allows opportunistic maintenance to run at any time in the scheduled maintenance window (all `PERIODIC` maintenance is set 28 days in advance). + +* `min_nodes_per_pool` (Required) - The minimum nodes required to be available in a pool. Blocks maintenance if it would cause the number of running nodes to dip below this value. + The `guest_accelerator` block supports: * `type` (Required) - The accelerator type resource to expose to this instance. E.g. `nvidia-tesla-k80`. diff --git a/mmv1/third_party/terraform/website/docs/r/sql_database_instance.html.markdown b/mmv1/third_party/terraform/website/docs/r/sql_database_instance.html.markdown index a58958c2ef18..5e7d32e7ae8a 100644 --- a/mmv1/third_party/terraform/website/docs/r/sql_database_instance.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/sql_database_instance.html.markdown @@ -496,6 +496,8 @@ The `settings` block supports: * `data_disk_provisioned_throughput` - (Optional) Provisioned throughput measured in MiB per second for the data disk. This field is only used for `HYPERDISK_BALANCED` disk types. +* `replication_lag_max_seconds` - (Optional) The acceptable replication lag, in seconds, after which a read replica recreates itself. The lag must persist for at least five minutes before recreation is triggered. This is a replica level field, and must be between `300` (five minutes) and `31536000` (one year). + * `node_count` - For a read pool instance, the number of nodes in the read pool. For read pools with auto scaling enabled, this field is read only. * `pricing_plan` - (Optional) Pricing plan for this instance, can only be `PER_USE`. diff --git a/mmv1/third_party/tgc_next/go.mod b/mmv1/third_party/tgc_next/go.mod index b1d611fec820..8ce879b5be11 100644 --- a/mmv1/third_party/tgc_next/go.mod +++ b/mmv1/third_party/tgc_next/go.mod @@ -108,13 +108,13 @@ require ( go.uber.org/multierr v1.10.0 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/crypto v0.55.0 // indirect - golang.org/x/mod v0.38.0 // indirect + golang.org/x/mod v0.40.0 // indirect golang.org/x/net v0.58.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.41.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.48.0 // indirect + golang.org/x/tools v0.49.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7 // indirect diff --git a/mmv1/third_party/tgc_next/go.sum b/mmv1/third_party/tgc_next/go.sum index 522d0cb697d9..e015f12b32f5 100644 --- a/mmv1/third_party/tgc_next/go.sum +++ b/mmv1/third_party/tgc_next/go.sum @@ -367,6 +367,8 @@ golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= +golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= +golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -418,6 +420,7 @@ golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa h1:efT73AJZfAAUV7SOip6pWGkwJDzIGiKBZGVzHYa+ve4= golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE= golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959 h1:RJhm5l6Fo4rmEIcndxDllNhhf/fAx8qIm4t6A7vpm2A= +golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5 h1:ZUSxONxc981v7AW7QUg+I9WwZzSTTJ019ENBYr5pV/Q= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -444,6 +447,8 @@ golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/tools/diff-processor/go.mod b/tools/diff-processor/go.mod index 7820e6d95bda..a8db44104fde 100644 --- a/tools/diff-processor/go.mod +++ b/tools/diff-processor/go.mod @@ -52,11 +52,11 @@ require ( github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - golang.org/x/mod v0.38.0 // indirect + golang.org/x/mod v0.40.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.41.0 // indirect - golang.org/x/tools v0.48.0 // indirect + golang.org/x/tools v0.49.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/tools/diff-processor/go.sum b/tools/diff-processor/go.sum index 3fbc63c2712c..5508719276cf 100644 --- a/tools/diff-processor/go.sum +++ b/tools/diff-processor/go.sum @@ -105,6 +105,8 @@ golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSO golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= +golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= +golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -142,6 +144,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= diff --git a/tools/test-reader/go.mod b/tools/test-reader/go.mod index ec9a4cbf7c73..0e53e2c2e671 100644 --- a/tools/test-reader/go.mod +++ b/tools/test-reader/go.mod @@ -16,8 +16,8 @@ require ( github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/zclconf/go-cty v1.13.0 // indirect - golang.org/x/mod v0.38.0 // indirect + golang.org/x/mod v0.40.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/text v0.41.0 // indirect - golang.org/x/tools v0.48.0 // indirect + golang.org/x/tools v0.49.0 // indirect ) diff --git a/tools/test-reader/go.sum b/tools/test-reader/go.sum index 57a3c3db897b..5ae68c0c10b1 100644 --- a/tools/test-reader/go.sum +++ b/tools/test-reader/go.sum @@ -26,13 +26,13 @@ github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0 github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= -golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= -golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= +golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= +golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= -golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= -golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=