From 2c5368c6e65bc9e13a938fb841551049a7bd3b5f Mon Sep 17 00:00:00 2001 From: Josh Kodroff Date: Wed, 19 Aug 2026 22:37:45 -0400 Subject: [PATCH 1/3] tfgen: fix Import sections for IAM-style resources Upstream documents a family of IAM resources on a single page, using quoted, space-delimited composite IDs. Four defects compounded there, so that a page like gcp:projects/iAMMember rendered no usable import guidance at all. parseImportCode's ID capture was a single whitespace-free token, so a quoted ID containing spaces never matched and the `terraform import` line was emitted verbatim. Widen the capture to accept a quoted string, keeping the quotes in the emitted command. That alone would make things worse: every example on a shared page would then parse, and each would be stamped with the token of the page being generated - so the IamMember example on the IamBinding page would become a silently wrong `pulumi import`. Resolve each example's own Terraform resource type through the provider's resource map instead, falling back to the page's token when the type is not one this provider bridges. Upstream also embeds `terraform import` examples in prose, outside any fence. Rewrite those code spans too. Finally, drop Terraform-only HCL `import { ... }` blocks whichever way their fence is tagged. Only ```terraform was recognized, but upstream overwhelmingly writes ```tf. Leaving one in did more than render stray HCL: isHCL treats tf/hcl as convertible, `pulumi convert` rejects import blocks, and convertExamples then strips the entire enclosing subsection. That is why the Import body vanished on pages whose every subsection opens with such a block. Fixes #3584 Fixes #3585 Co-Authored-By: Claude Opus 5 (1M context) --- pkg/tfgen/docs.go | 118 ++++- pkg/tfgen/import_code_test.go | 406 ++++++++++++++++++ .../parse-imports/gcp-project-iam.md | 151 +++++++ .../random-string-full-expected.md | 4 +- 4 files changed, 660 insertions(+), 19 deletions(-) create mode 100644 pkg/tfgen/import_code_test.go create mode 100644 pkg/tfgen/test_data/parse-imports/gcp-project-iam.md diff --git a/pkg/tfgen/docs.go b/pkg/tfgen/docs.go index f05dae6ea..9a2cb3921 100644 --- a/pkg/tfgen/docs.go +++ b/pkg/tfgen/docs.go @@ -1203,7 +1203,8 @@ func (p *tfMarkdownParser) parseImports(body string) { if token == "" { token = "MISSING_TOK" } - if rewritten, ok := rewriteImportMarkdown(body, token); ok { + toks := importTokens{typeToken: token, resources: p.infoCtx.info.Resources} + if rewritten, ok := rewriteImportMarkdown(body, toks); ok { p.ret.Import = rewritten } } @@ -1231,7 +1232,7 @@ var ( // ```sh // $ pulumi import snowflake:index/apiIntegration:ApiIntegration example name // ``` -func rewriteImportMarkdown(body, typeToken string) (string, bool) { +func rewriteImportMarkdown(body string, toks importTokens) (string, bool) { if body == "" { return "", false } @@ -1259,7 +1260,7 @@ func rewriteImportMarkdown(body, typeToken string) (string, bool) { fenceLines = fenceLines[:0] continue } - out = append(out, line) + out = append(out, rewriteInlineImportCode(line, toks)) continue } @@ -1267,7 +1268,7 @@ func rewriteImportMarkdown(body, typeToken string) (string, bool) { // This acts as a post-processor for the collected fence lines. if fenceEnd.MatchString(line) { rewritten, keep, dropSyntaxLine := rewriteImportFence( - fenceStartLine, line, fenceIndent, fenceInfo, fenceLines, typeToken) + fenceStartLine, line, fenceIndent, fenceInfo, fenceLines, toks) if keep { if dropSyntaxLine { out = dropImportSyntaxLine(out) @@ -1334,7 +1335,7 @@ func rewriteImportMarkdown(body, typeToken string) (string, bool) { // # Example: // $ pulumi import auth0/index/pages:Pages my_pages "22f4f21b-017a-319d-92e7-2291c1ca36c4" func rewriteImportFence( - startLine, endLine, indent, info string, lines []string, typeToken string, + startLine, endLine, indent, info string, lines []string, toks importTokens, ) ([]string, bool, bool) { if fields := strings.Fields(info); len(fields) > 0 { info = fields[0] @@ -1342,7 +1343,7 @@ func rewriteImportFence( info = "" } code := strings.Join(lines, "\n") - if info == "terraform" && looksLikeTerraformImportBlock(code) { + if isHCLFenceInfo(info) && looksLikeTerraformImportBlock(code) { return nil, false, false } if info == "" || info == "console" || info == "shell" || info == "sh" || info == "bash" { @@ -1354,7 +1355,7 @@ func rewriteImportFence( } hoistMode := hasHoistableImportComments(lines) if hoistMode { - if rewritten, updated := rewriteImportFenceWithHoistedComments(lines, indent, typeToken); updated { + if rewritten, updated := rewriteImportFenceWithHoistedComments(lines, indent, toks); updated { return rewritten, true, true } } @@ -1367,7 +1368,7 @@ func rewriteImportFence( // # Example: // $ pulumi import auth0/index/pages:Pages my_pages "..." comments, remaining, hoist := extractImportFenceComments(lines, indent) - rewritten, updated := rewriteImportLines(remaining, typeToken) + rewritten, updated := rewriteImportLines(remaining, toks) if updated { out := make([]string, 0, len(rewritten)+2) if hoist && len(comments) > 0 { @@ -1407,7 +1408,7 @@ func hasHoistableImportComments(lines []string) bool { return false } -func rewriteImportFenceWithHoistedComments(lines []string, indent, typeToken string) ([]string, bool) { +func rewriteImportFenceWithHoistedComments(lines []string, indent string, toks importTokens) ([]string, bool) { var out []string updated := false remaining := lines @@ -1460,7 +1461,7 @@ func rewriteImportFenceWithHoistedComments(lines []string, indent, typeToken str continue } chunk, hadTrailingBlank := trimTrailingBlankLines(chunk) - rewritten, chunkUpdated := rewriteImportLines(chunk, typeToken) + rewritten, chunkUpdated := rewriteImportLines(chunk, toks) if chunkUpdated { updated = true } @@ -1479,7 +1480,7 @@ func rewriteImportFenceWithHoistedComments(lines []string, indent, typeToken str return out, updated } -func rewriteImportLines(lines []string, typeToken string) ([]string, bool) { +func rewriteImportLines(lines []string, toks importTokens) ([]string, bool) { updated := false rewritten := make([]string, 0, len(lines)) for i := 0; i < len(lines); { @@ -1497,7 +1498,8 @@ func rewriteImportLines(lines []string, typeToken string) ([]string, bool) { cmd := strings.Join(cmdLines, "\n") if parsed, ok := parseImportCode(cmd); ok { rewritten = append(rewritten, - fmt.Sprintf("%s$ pulumi import %s %s %s", leading, typeToken, parsed.Name, parsed.ID)) + fmt.Sprintf("%s$ pulumi import %s %s %s", + leading, toks.tokenFor(parsed.Type), parsed.Name, parsed.ID)) updated = true i = j + 1 continue @@ -1516,6 +1518,35 @@ func rewriteImportLines(lines []string, typeToken string) ([]string, bool) { return rewritten, updated } +// inlineImportCodeSpan matches a backtick-delimited code span in prose. +var inlineImportCodeSpan = regexp.MustCompile("`[^`]*`") + +// rewriteInlineImportCode rewrites `terraform import` examples that upstream embeds in prose +// rather than in a code fence. Left alone they leak the Terraform CLI and an upstream +// resource name into the rendered Import section. +// +// Example (input): +// +// e.g. `terraform import google_project_iam_binding.my_project "{{project}} roles/{{role}} title"` +// +// Example (output): +// +// e.g. `pulumi import gcp:projects/iAMBinding:IAMBinding my_project "{{project}} roles/{{role}} title"` +func rewriteInlineImportCode(line string, toks importTokens) string { + if !strings.Contains(line, "terraform import") && !strings.Contains(line, "pulumi import") { + return line + } + return inlineImportCodeSpan.ReplaceAllStringFunc(line, func(span string) string { + code := strings.TrimSuffix(strings.TrimPrefix(span, "`"), "`") + parsed, ok := parseImportCode(code) + if !ok { + return span + } + return fmt.Sprintf("`pulumi import %s %s %s`", + toks.tokenFor(parsed.Type), parsed.Name, parsed.ID) + }) +} + // extractImportFenceComments hoists leading comment lines (starting with '#') from a fence. // // Example (input): @@ -1657,6 +1688,19 @@ func dropImportSyntaxLine(lines []string) []string { return out } +// isHCLFenceInfo reports whether a fence info string tags the block as HCL. Upstream tags +// these fences `tf` far more often than `terraform`, and the distinction matters: an HCL +// block left in the Import section is handed to convertExamples, which strips the entire +// enclosing subsection when the conversion fails. +func isHCLFenceInfo(info string) bool { + switch info { + case "terraform", "tf", "hcl": + return true + default: + return false + } +} + // looksLikeTerraformImportBlock detects the HCL `import { ... }` syntax so it can be dropped. // // Example: @@ -1689,8 +1733,13 @@ func looksLikeTerraformImportBlock(code string) bool { // $ terraform import \ // some_resource.name \ // +// +// The ID is either a single whitespace-free token or a quoted string. Quoting is what lets a +// composite ID contain spaces, as IAM-style resources do: +// +// $ terraform import google_project_iam_member.default "{{project}} roles/viewer user:jane@example.com" var importCodePattern = regexp.MustCompile( - `^\s*(?:[%$]\s+)?(?:pulumi|terraform) import[\\\s]+([^.]+)[.]([^\s]+)[\\\s]+([^\s]+)\s*$`) + `^\s*(?:[%$]\s+)?(?:pulumi|terraform) import[\\\s]+([^.]+)[.]([^\s]+)[\\\s]+("[^"]*"|'[^']*'|[^\s]+)\s*$`) // Recognize import example codeblocks. // @@ -1698,22 +1747,57 @@ var importCodePattern = regexp.MustCompile( // // s := "% pulumi import aws_accessanalyzer_analyzer.example exampleID" // v, ok := parseImportCode(s) +// v.Type == "aws_accessanalyzer_analyzer" // v.Name == "example" // v.ID == "exampleID" func parseImportCode(code string) (struct { + Type string Name string ID string }, bool, ) { type ret struct { + Type string Name string ID string } - if importCodePattern.MatchString(code) { - matches := importCodePattern.FindStringSubmatch(code) - return ret{Name: matches[2], ID: matches[3]}, true + matches := importCodePattern.FindStringSubmatch(code) + if matches == nil { + return ret{}, false } - return ret{}, false + // A line-continuation example puts the resource address on its own line, so the type + // capture can pick up surrounding whitespace and backslashes. + return ret{ + Type: strings.Trim(matches[1], " \t\n\\"), + Name: matches[2], + ID: matches[3], + }, true +} + +// importTokens resolves the Pulumi type token to stamp onto a rewritten import example. +// +// Upstream frequently documents a family of resources on a single page - the Google IAM +// "triad" (`google_*_iam_member`, `google_*_iam_binding`, `google_*_iam_policy`) is the +// common case - so an example found on one page often belongs to a sibling resource. Naming +// the page's own resource there produces a command that does not do what it says, so prefer +// the token the example's own Terraform resource maps to. +type importTokens struct { + // typeToken is the token of the resource whose docs are being generated. + typeToken string + // resources maps upstream Terraform resource names to their bridged Pulumi resources. + // It is nil when the provider's resource map is unavailable, in which case every + // example falls back to typeToken. + resources map[string]*tfbridge.ResourceInfo +} + +// tokenFor returns the token to use for an example importing the given Terraform resource +// type, falling back to the token of the resource being documented when that type is not a +// resource this provider bridges. +func (t importTokens) tokenFor(tfResourceType string) string { + if r, ok := t.resources[tfResourceType]; ok && r != nil && r.Tok != "" { + return r.Tok.String() + } + return t.typeToken } func (p *tfMarkdownParser) parseFrontMatter(subsection []string) { diff --git a/pkg/tfgen/import_code_test.go b/pkg/tfgen/import_code_test.go new file mode 100644 index 000000000..7dfcb8013 --- /dev/null +++ b/pkg/tfgen/import_code_test.go @@ -0,0 +1,406 @@ +// Copyright 2016-2025, Pulumi Corporation. +// +// 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. + +package tfgen + +import ( + "runtime" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge" +) + +// TestParseImportCode pins down importCodePattern. The import section of a bridged provider's +// docs is only as good as this regex: a line it fails to recognize is emitted verbatim, which +// leaves a `terraform import` command on a Pulumi docs page. +func TestParseImportCode(t *testing.T) { + t.Parallel() + + type expect struct { + typ string + name string + id string + } + tests := []struct { + name string + code string + // want is nil when the line is expected not to parse as an import example. + want *expect + }{ + // --- Shapes that already worked, pinned against regression. --- + { + name: "bare terraform import", + code: "terraform import snowflake_api_integration.example name", + want: &expect{typ: "snowflake_api_integration", name: "example", id: "name"}, + }, + { + name: "dollar prompt", + code: "$ terraform import snowflake_api_integration.example name", + want: &expect{typ: "snowflake_api_integration", name: "example", id: "name"}, + }, + { + name: "percent prompt", + code: "% terraform import aws_accessanalyzer_analyzer.example exampleID", + want: &expect{typ: "aws_accessanalyzer_analyzer", name: "example", id: "exampleID"}, + }, + { + name: "pulumi import is recognized too", + code: "$ pulumi import aws_accessanalyzer_analyzer.example exampleID", + want: &expect{typ: "aws_accessanalyzer_analyzer", name: "example", id: "exampleID"}, + }, + { + name: "leading indentation", + code: " terraform import aws_lb.bar my-load-balancer", + want: &expect{typ: "aws_lb", name: "bar", id: "my-load-balancer"}, + }, + { + name: "trailing whitespace", + code: "terraform import aws_lb.bar my-load-balancer ", + want: &expect{typ: "aws_lb", name: "bar", id: "my-load-balancer"}, + }, + { + name: "line continuations", + code: "$ terraform import \\\n some_resource.name \\\n some-ID", + want: &expect{typ: "some_resource", name: "name", id: "some-ID"}, + }, + { + name: "id containing dots and slashes", + code: "% terraform import aws_lb.bar " + + "arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188", + want: &expect{ + typ: "aws_lb", + name: "bar", + id: "arn:aws:elasticloadbalancing:us-west-2:123456789012:" + + "loadbalancer/app/my-load-balancer/50dc6c495c0c9188", + }, + }, + { + name: "single quoted id without spaces keeps its quotes", + code: "terraform import snowflake_account_grant.example 'accountName|||USAGE|true'", + want: &expect{typ: "snowflake_account_grant", name: "example", id: "'accountName|||USAGE|true'"}, + }, + { + name: "double quoted id without spaces keeps its quotes", + code: `terraform import auth0_pages.my_pages "22f4f21b-017a-319d-92e7-2291c1ca36c4"`, + want: &expect{ + typ: "auth0_pages", + name: "my_pages", + id: `"22f4f21b-017a-319d-92e7-2291c1ca36c4"`, + }, + }, + { + name: "angle bracket placeholder id", + code: "$ terraform import some_resource.name ", + want: &expect{typ: "some_resource", name: "name", id: ""}, + }, + { + name: "curly brace placeholder id", + code: "terraform import google_project_iam_policy.default {{project_id}}", + want: &expect{typ: "google_project_iam_policy", name: "default", id: "{{project_id}}"}, + }, + { + name: "resource name containing dots", + code: "terraform import aws_instance.my.instance i-abc123", + want: &expect{typ: "aws_instance", name: "my.instance", id: "i-abc123"}, + }, + + // --- Space-delimited, quoted IDs: the bug in #3584. --- + { + name: "double quoted id with spaces (IAM member)", + code: `terraform import google_project_iam_member.default "{{project_id}} roles/viewer user:foo@example.com"`, + want: &expect{ + typ: "google_project_iam_member", + name: "default", + id: `"{{project_id}} roles/viewer user:foo@example.com"`, + }, + }, + { + name: "double quoted id with spaces (IAM binding)", + code: `terraform import google_project_iam_binding.default "{{project_id}} roles/viewer"`, + want: &expect{ + typ: "google_project_iam_binding", + name: "default", + id: `"{{project_id}} roles/viewer"`, + }, + }, + { + name: "double quoted id with spaces and a prompt", + code: `$ terraform import google_storage_bucket_iam_member.editor ` + + `"b/{{bucket}} roles/storage.objectViewer user:jane@example.com"`, + want: &expect{ + typ: "google_storage_bucket_iam_member", + name: "editor", + id: `"b/{{bucket}} roles/storage.objectViewer user:jane@example.com"`, + }, + }, + { + name: "single quoted id with spaces", + code: `terraform import google_project_iam_binding.default '{{project_id}} roles/viewer'`, + want: &expect{ + typ: "google_project_iam_binding", + name: "default", + id: `'{{project_id}} roles/viewer'`, + }, + }, + { + name: "quoted id with spaces across line continuations", + code: "$ terraform import \\\n google_project_iam_member.default \\\n" + + ` "{{project_id}} roles/viewer user:foo@example.com"`, + want: &expect{ + typ: "google_project_iam_member", + name: "default", + id: `"{{project_id}} roles/viewer user:foo@example.com"`, + }, + }, + { + name: "quoted id with trailing whitespace", + code: `terraform import google_project_iam_binding.default "{{project_id}} roles/viewer" `, + want: &expect{ + typ: "google_project_iam_binding", + name: "default", + id: `"{{project_id}} roles/viewer"`, + }, + }, + + // --- Shapes that must keep NOT parsing, so we do not emit a bogus command. --- + { + name: "not an import command", + code: "terraform plan", + }, + { + name: "import with no id", + code: "terraform import aws_lb.bar", + }, + { + name: "unquoted id containing spaces stays ambiguous", + code: "terraform import google_project_iam_member.default {{project_id}} roles/viewer", + }, + { + name: "two quoted arguments", + code: `terraform import google_project_iam_member.default "a" "b"`, + }, + { + name: "unterminated quote", + code: `terraform import google_project_iam_member.default "{{project_id}} roles/viewer`, + }, + { + name: "trailing comment after a quoted id", + code: `terraform import google_project_iam_member.default "a b" # do this`, + }, + { + name: "resource address without a dot", + code: "terraform import aws_lb my-load-balancer", + }, + { + name: "hcl import block", + code: "import {\n to = aws_iam_role.example\n id = \"developer_name\"\n}", + }, + { + name: "prose mentioning the command", + code: "Use terraform import to bring the resource under management", + }, + { + name: "empty", + code: "", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, ok := parseImportCode(tt.code) + if tt.want == nil { + assert.Falsef(t, ok, "expected %q not to parse, got %+v", tt.code, got) + return + } + require.Truef(t, ok, "expected %q to parse as an import example", tt.code) + assert.Equal(t, tt.want.typ, got.Type, "terraform resource type") + assert.Equal(t, tt.want.name, got.Name, "resource name") + assert.Equal(t, tt.want.id, got.ID, "import ID") + }) + } +} + +// gcpProjectIAMResources is the subset of pulumi-gcp's resource map that the upstream +// google_project_iam docs page covers. Upstream documents the whole IAM family on one page, +// so generating any one of these resources has to reach for its siblings' tokens. +var gcpProjectIAMResources = map[string]*tfbridge.ResourceInfo{ + "google_project_iam_member": {Tok: "gcp:projects/iAMMember:IAMMember"}, + "google_project_iam_binding": {Tok: "gcp:projects/iAMBinding:IAMBinding"}, + "google_project_iam_policy": {Tok: "gcp:projects/iAMPolicy:IAMPolicy"}, + "google_project_iam_audit_config": {Tok: "gcp:projects/iAMAuditConfig:IAMAuditConfig"}, +} + +// TestParseImports_SpaceDelimitedIDs covers https://github.com/pulumi/pulumi-terraform-bridge/issues/3584 +// against the real upstream google_project_iam docs page. +func TestParseImports_SpaceDelimitedIDs(t *testing.T) { + t.Parallel() + if runtime.GOOS == "windows" { + t.Skipf("Skipping on windows - test case needs to be made robust to newline handling") + } + + input := readfile(t, "test_data/parse-imports/gcp-project-iam.md") + + parser := tfMarkdownParser{ + info: &mockResource{token: "gcp:projects/iAMMember:IAMMember"}, + rawname: "google_project_iam_member", + infoCtx: infoContext{ + pkg: "gcp", + language: "nodejs", + info: tfbridge.ProviderInfo{ + Name: "gcp", + Resources: gcpProjectIAMResources, + }, + }, + } + parser.parseImports(input) + actual := parser.ret.Import + + // Every example on the page is rewritten, each stamped with the token of the resource + // it actually imports rather than the token of the page being generated. + for _, want := range []string{ + "$ pulumi import gcp:projects/iAMMember:IAMMember default " + + `"{{project_id}} roles/viewer user:foo@example.com"`, + `$ pulumi import gcp:projects/iAMBinding:IAMBinding default "{{project_id}} roles/viewer"`, + "$ pulumi import gcp:projects/iAMPolicy:IAMPolicy default {{project_id}}", + "$ pulumi import gcp:projects/iAMAuditConfig:IAMAuditConfig default " + + `"{{project_id}} foo.googleapis.com"`, + } { + assert.Containsf(t, actual, want, "expected import example for %q", want) + } + + // The `terraform import` example upstream embeds in prose, outside any code fence, is + // rewritten as well. + assert.Contains(t, actual, + "`pulumi import gcp:projects/iAMBinding:IAMBinding my_project "+ + `"{{your-project-id}} roles/{{role_id}} condition-title"`+"`") + + // No import command may refer the reader to the Terraform CLI, and none may name an + // upstream resource instead of a Pulumi token. + assert.NotContains(t, actual, "terraform import") + + // Nor may it carry Terraform-only `import { ... }` blocks; see issue #3585. + assert.NotContains(t, actual, "import {") + assert.NotContains(t, actual, "```tf") + + for _, line := range strings.Split(actual, "\n") { + if !strings.Contains(line, "pulumi import") { + continue + } + assert.NotContainsf(t, line, "google_", "import command names an upstream resource") + } +} + +// TestParseImports_DropsHCLImportBlocks covers +// https://github.com/pulumi/pulumi-terraform-bridge/issues/3585. +// +// A Terraform `import { ... }` block is Terraform-only config syntax with no Pulumi analogue, +// so it has to be dropped from the Import section. Upstream tags these fences ```tf far more +// often than ```terraform, and leaving one in does more than render stray HCL: isHCL treats +// tf/hcl as convertible, the conversion cannot succeed, and convertExamples then strips the +// entire enclosing subsection - heading, prose and sibling examples included. +func TestParseImports_DropsHCLImportBlocks(t *testing.T) { + t.Parallel() + if runtime.GOOS == "windows" { + t.Skipf("Skipping on windows - test cases need to be made robust to newline handling") + } + + for _, fence := range []string{"terraform", "tf", "hcl"} { + fence := fence + t.Run(fence, func(t *testing.T) { + t.Parallel() + input := strings.Join([]string{ + "", + "### Importing IAM members", + "", + "An `import` block (Terraform v1.5.0 and later) can be used:", + "", + "```" + fence, + "import {", + ` id = "{{project_id}} roles/viewer user:foo@example.com"`, + " to = google_project_iam_member.default", + "}", + "```", + "", + "The command can also be used:", + "", + "```sh", + `terraform import google_project_iam_member.default ` + + `"{{project_id}} roles/viewer user:foo@example.com"`, + "```", + "", + }, "\n") + + parser := tfMarkdownParser{ + info: &mockResource{token: "gcp:projects/iAMMember:IAMMember"}, + rawname: "google_project_iam_member", + infoCtx: infoContext{ + pkg: "gcp", + info: tfbridge.ProviderInfo{Name: "gcp", Resources: gcpProjectIAMResources}, + }, + } + parser.parseImports(input) + actual := parser.ret.Import + + // The Terraform-only import block is gone, fence and all. + assert.NotContains(t, actual, "import {") + assert.NotContains(t, actual, "to = google_project_iam_member.default") + assert.NotContains(t, actual, "```"+fence) + + // Everything around it survives: the heading, the prose, and the shell + // example, which is still rewritten to a pulumi import command. + assert.Contains(t, actual, "### Importing IAM members") + assert.Contains(t, actual, "The command can also be used:") + assert.Contains(t, actual, + "$ pulumi import gcp:projects/iAMMember:IAMMember default "+ + `"{{project_id}} roles/viewer user:foo@example.com"`) + }) + } +} + +// TestParseImports_UnmappedResourceFallsBackToPageToken pins the fallback: when an example +// names a Terraform resource this provider does not bridge, we keep stamping the token of the +// page being generated rather than dropping the example. +func TestParseImports_UnmappedResourceFallsBackToPageToken(t *testing.T) { + t.Parallel() + if runtime.GOOS == "windows" { + t.Skipf("Skipping on windows - test case needs to be made robust to newline handling") + } + + input := strings.Join([]string{ + "", + "```sh", + `terraform import google_some_unmapped_resource.default "a b"`, + "```", + "", + }, "\n") + + parser := tfMarkdownParser{ + info: &mockResource{token: "gcp:projects/iAMMember:IAMMember"}, + rawname: "google_project_iam_member", + infoCtx: infoContext{ + pkg: "gcp", + info: tfbridge.ProviderInfo{Name: "gcp", Resources: gcpProjectIAMResources}, + }, + } + parser.parseImports(input) + + assert.Contains(t, parser.ret.Import, + `$ pulumi import gcp:projects/iAMMember:IAMMember default "a b"`) +} diff --git a/pkg/tfgen/test_data/parse-imports/gcp-project-iam.md b/pkg/tfgen/test_data/parse-imports/gcp-project-iam.md new file mode 100644 index 000000000..2ef4bfba9 --- /dev/null +++ b/pkg/tfgen/test_data/parse-imports/gcp-project-iam.md @@ -0,0 +1,151 @@ +## Import + +-> **Custom Roles** If you're importing a IAM resource with a custom role, make sure to use the + full name of the custom role, e.g. `[projects/my-project|organizations/my-org]/roles/my-custom-role`. + +-> **Conditional IAM Bindings**: If you're importing a IAM binding with a condition block, make sure + to include the title of condition, e.g. `terraform import google_project_iam_binding.my_project "{{your-project-id}} roles/{{role_id}} condition-title"` + +### Importing IAM members + +IAM member imports use space-delimited identifiers that contain the resource's `project_id`, `role`, and `member` e.g. + +* `"{{project_id}} roles/viewer user:foo@example.com"` + +An [`import` block](https://developer.hashicorp.com/terraform/language/import) (Terraform v1.5.0 and later) can be used to import IAM members: + +```tf +import { + id = "{{project_id}} roles/viewer user:foo@example.com" + to = google_project_iam_member.default +} +``` + +The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can also be used: + +``` +$ terraform import google_project_iam_member.default "{{project_id}} roles/viewer user:foo@example.com" +``` + +#### Import via resource identity + +`google_project_iam_member` also supports plannable import via [resource identity](https://developer.hashicorp.com/terraform/language/block/import#identity) (Terraform 1.12+): + +```tf +import { + to = google_project_iam_member.default + identity = { + project = "your-project-id" + role = "roles/viewer" + member = "user:foo@example.com" + } +} +``` + +Identity attributes: + +* `project` - (Optional) The project id. May be omitted if a default project is configured on the provider. +* `role` - (Required) The IAM role being granted. +* `member` - (Required) The identity that the role is granted to. +* `condition_title` - (Optional) Title of the IAM condition, when importing a conditional binding. + +### Importing IAM bindings + +IAM binding imports use space-delimited identifiers that contain the `org_id` and role, e.g. + +* `"{{project_id}} roles/viewer"` + +An [`import` block](https://developer.hashicorp.com/terraform/language/import) (Terraform v1.5.0 and later) can be used to import IAM bindings: + +```tf +import { + id = "{{project_id}} roles/viewer" + to = google_project_iam_binding.default +} +``` + +The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can also be used: + +``` +terraform import google_project_iam_binding.default "{{project_id}} roles/viewer" +``` + +#### Import via resource identity + +`google_project_iam_binding` also supports plannable import via [resource identity](https://developer.hashicorp.com/terraform/language/block/import#identity) (Terraform 1.12+): + +```tf +import { + to = google_project_iam_binding.default + identity = { + project = "your-project-id" + role = "roles/viewer" + } +} +``` + +Identity attributes: + +* `project` - (Optional) The project id. May be omitted if a default project is configured on the provider. +* `role` - (Required) The IAM role being granted. +* `condition_title` - (Optional) Title of the IAM condition, when importing a conditional binding. + +### Importing IAM policies + +IAM policy imports use the identifier of the Project only. For example: + +* `"{{project_id}}"` + +An [`import` block](https://developer.hashicorp.com/terraform/language/import) (Terraform v1.5.0 and later) can be used to import IAM policies: + +```tf +import { + id = "{{project_id}}" + to = google_project_iam_policy.default +} +``` + +The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can also be used: + +``` +$ terraform import google_project_iam_policy.default {{project_id}} +``` + +#### Import via resource identity + +`google_project_iam_policy` also supports plannable import via [resource identity](https://developer.hashicorp.com/terraform/language/block/import#identity) (Terraform 1.12+): + +```tf +import { + to = google_project_iam_policy.default + identity = { + project = "your-project-id" + } +} +``` + +Identity attributes: + +* `project` - (Optional) The project id. May be omitted if a default project is configured on the provider. + +### Importing Audit Configs + +An audit config can be imported into a `google_project_iam_audit_config` resource using the resource's `project_id` and the `service`, e.g: + +* `"{{project_id}} foo.googleapis.com"` + + +An [`import` block](https://developer.hashicorp.com/terraform/language/import) (Terraform v1.5.0 and later) can be used to import audit configs: + +```tf +import { + id = "{{project_id}} foo.googleapis.com" + to = google_project_iam_audit_config.default +} +``` + +The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can also be used: + +``` +terraform import google_project_iam_audit_config.default "{{project_id}} foo.googleapis.com" +``` diff --git a/pkg/tfgen/test_data/parse-imports/random-string-full-expected.md b/pkg/tfgen/test_data/parse-imports/random-string-full-expected.md index b7a56c3d5..456f7e341 100644 --- a/pkg/tfgen/test_data/parse-imports/random-string-full-expected.md +++ b/pkg/tfgen/test_data/parse-imports/random-string-full-expected.md @@ -18,13 +18,13 @@ resource "random_string" "test" { } ``` -Then importing the resource using `terraform import random_string.test test`, +Then importing the resource using `pulumi import random:index/string:String test test`, would result in the triggering of a replacement (i.e., destroy-create) during the next `pulumi up`. ### Avoiding Replacement -If the resource were imported using `terraform import random_string.test test`, +If the resource were imported using `pulumi import random:index/string:String test test`, replacement can be avoided by using: 1. Attribute values that match the imported ID and defaults: From 89b37d674dddb9cdb85abc1c58f51900066803ad Mon Sep 17 00:00:00 2001 From: Josh Kodroff Date: Wed, 19 Aug 2026 22:44:35 -0400 Subject: [PATCH 2/3] tfgen: address review feedback Scope the importTokens doc comment to Google Cloud, which is the only provider observed to share one upstream page across an IAM triad, and correct the copyright year on the new test file. Co-Authored-By: Claude Opus 5 (1M context) --- pkg/tfgen/docs.go | 10 +++++----- pkg/tfgen/import_code_test.go | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/tfgen/docs.go b/pkg/tfgen/docs.go index 9a2cb3921..870886f64 100644 --- a/pkg/tfgen/docs.go +++ b/pkg/tfgen/docs.go @@ -1776,11 +1776,11 @@ func parseImportCode(code string) (struct { // importTokens resolves the Pulumi type token to stamp onto a rewritten import example. // -// Upstream frequently documents a family of resources on a single page - the Google IAM -// "triad" (`google_*_iam_member`, `google_*_iam_binding`, `google_*_iam_policy`) is the -// common case - so an example found on one page often belongs to a sibling resource. Naming -// the page's own resource there produces a command that does not do what it says, so prefer -// the token the example's own Terraform resource maps to. +// Google Cloud documents its IAM "triad" (`google_*_iam_member`, `google_*_iam_binding`, +// `google_*_iam_policy`) on one shared upstream page, so an example found on any of those +// pages often belongs to a sibling resource. Naming the page's own resource there produces a +// command that does not do what it says, so prefer the token the example's own Terraform +// resource maps to. type importTokens struct { // typeToken is the token of the resource whose docs are being generated. typeToken string diff --git a/pkg/tfgen/import_code_test.go b/pkg/tfgen/import_code_test.go index 7dfcb8013..825971d96 100644 --- a/pkg/tfgen/import_code_test.go +++ b/pkg/tfgen/import_code_test.go @@ -1,4 +1,4 @@ -// Copyright 2016-2025, Pulumi Corporation. +// Copyright 2016-2026, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From 1842b79186e12834041f98a08eb5418ec97ccf7a Mon Sep 17 00:00:00 2001 From: Josh Kodroff Date: Thu, 20 Aug 2026 10:46:35 -0400 Subject: [PATCH 3/3] tfgen: refresh dynamic random golden for prose import rewrites The Import-section fix rewrites `terraform import` code spans that appear in prose, outside a fence. The random provider's password and string docs contain exactly those, so TestSchemaGenerationFullDocs needs its golden refreshed alongside the tfgen test_data fixture. Regenerated with `make test_accept`, whose scoped .pulumi-test plugin cache keeps converted examples identical to CI's. Co-Authored-By: Claude Opus 5 (1M context) --- .../hashicorp/random-3.6.3.golden | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dynamic/testdata/TestSchemaGenerationFullDocs/hashicorp/random-3.6.3.golden b/dynamic/testdata/TestSchemaGenerationFullDocs/hashicorp/random-3.6.3.golden index e73866552..76bb2c79c 100644 --- a/dynamic/testdata/TestSchemaGenerationFullDocs/hashicorp/random-3.6.3.golden +++ b/dynamic/testdata/TestSchemaGenerationFullDocs/hashicorp/random-3.6.3.golden @@ -314,7 +314,7 @@ } }, "random:index/password:Password": { - "description": "Identical to\u003cspan pulumi-lang-nodejs=\" random.String \" pulumi-lang-dotnet=\" random.String \" pulumi-lang-go=\" String \" pulumi-lang-python=\" String \" pulumi-lang-yaml=\" random.String \" pulumi-lang-java=\" random.String \" pulumi-lang-hcl=\" random_string \"\u003e random.String \u003c/span\u003ewith the exception that the result is treated as sensitive and, thus, _not_ displayed in console output. Read more about sensitive data handling in the Terraform documentation.\n\nThis resource *does* use a cryptographic random number generator.\n\n## Example Usage\n\n\u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as aws from \"@pulumi/aws\";\nimport * as random from \"@pulumi/random\";\n\nconst password = new random.Password(\"password\", {\n length: 16,\n special: true,\n overrideSpecial: \"!#$%\u0026*()-_=+[]{}\u003c\u003e:?\",\n});\nconst example = new aws.rds.Instance(\"example\", {\n instanceClass: aws.rds.InstanceType.T3_Micro,\n allocatedStorage: 64,\n engine: \"mysql\",\n username: \"someone\",\n password: password.result,\n});\n```\n```python\nimport pulumi\nimport pulumi_aws as aws\nimport pulumi_random as random\n\npassword = random.Password(\"password\",\n length=float(16),\n special=True,\n override_special=\"!#$%\u0026*()-_=+[]{}\u003c\u003e:?\")\nexample = aws.rds.Instance(\"example\",\n instance_class=aws.rds.InstanceType.T3_MICRO,\n allocated_storage=64,\n engine=\"mysql\",\n username=\"someone\",\n password=password.result)\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Aws = Pulumi.Aws;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var password = new Random.Password(\"password\", new()\n {\n Length = 16,\n Special = true,\n OverrideSpecial = \"!#$%\u0026*()-_=+[]{}\u003c\u003e:?\",\n });\n\n var example = new Aws.Rds.Instance(\"example\", new()\n {\n InstanceClass = Aws.Rds.InstanceType.T3_Micro,\n AllocatedStorage = 64,\n Engine = \"mysql\",\n Username = \"someone\",\n Password = password.Result,\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/rds\"\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\tpassword, err := random.NewPassword(ctx, \"password\", \u0026random.PasswordArgs{\n\t\t\tLength: pulumi.Float64(16),\n\t\t\tSpecial: pulumi.Bool(true),\n\t\t\tOverrideSpecial: pulumi.String(\"!#$%\u0026*()-_=+[]{}\u003c\u003e:?\"),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = rds.NewInstance(ctx, \"example\", \u0026rds.InstanceArgs{\n\t\t\tInstanceClass: pulumi.String(rds.InstanceType_T3_Micro),\n\t\t\tAllocatedStorage: pulumi.Int(64),\n\t\t\tEngine: pulumi.String(\"mysql\"),\n\t\t\tUsername: pulumi.String(\"someone\"),\n\t\t\tPassword: password.Result,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n aws = {\n source = \"pulumi/aws\"\n }\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_password\" \"password\" {\n length = 16\n special = true\n override_special = \"!#$%\u0026*()-_=+[]{}\u003c\u003e:?\"\n}\nresource \"aws_rds_instance\" \"example\" {\n instance_class = \"db.t3.micro\"\n allocated_storage = 64\n engine = \"mysql\"\n username = \"someone\"\n password = random_password.password.result\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.Password;\nimport com.pulumi.random.PasswordArgs;\nimport com.pulumi.aws.rds.Instance;\nimport com.pulumi.aws.rds.InstanceArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var password = new Password(\"password\", PasswordArgs.builder()\n .length(16.0)\n .special(true)\n .overrideSpecial(\"!#$%\u0026*()-_=+[]{}\u003c\u003e:?\")\n .build());\n\n var example = new Instance(\"example\", InstanceArgs.builder()\n .instanceClass(\"db.t3.micro\")\n .allocatedStorage(64)\n .engine(\"mysql\")\n .username(\"someone\")\n .password(password.result())\n .build());\n\n }\n}\n```\n```yaml\nresources:\n password:\n type: random:Password\n properties:\n length: 16\n special: true\n overrideSpecial: '!#$%\u0026*()-_=+[]{}\u003c\u003e:?'\n example:\n type: aws:rds:Instance\n properties:\n instanceClass: db.t3.micro\n allocatedStorage: 64\n engine: mysql\n username: someone\n password: ${password.result}\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\n## Import\n\n```sh\n$ pulumi import random:index/password:Password password securepassword\n```\n\n### Limitations of Import\n\nAny attribute values that are specified within Terraform config will be\nignored during import and all attributes that have defaults defined within\nthe schema will have the default assigned.\n\nFor instance, using the following config during import:\n\u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as random from \"@pulumi/random\";\n\nconst password = new random.Password(\"password\", {\n length: 16,\n lower: false,\n});\n```\n```python\nimport pulumi\nimport pulumi_random as random\n\npassword = random.Password(\"password\",\n length=float(16),\n lower=False)\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var password = new Random.Password(\"password\", new()\n {\n Length = 16,\n Lower = false,\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := random.NewPassword(ctx, \"password\", \u0026random.PasswordArgs{\n\t\t\tLength: pulumi.Float64(16),\n\t\t\tLower: pulumi.Bool(false),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_password\" \"password\" {\n length = 16\n lower = false\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.Password;\nimport com.pulumi.random.PasswordArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var password = new Password(\"password\", PasswordArgs.builder()\n .length(16.0)\n .lower(false)\n .build());\n\n }\n}\n```\n```yaml\nresources:\n password:\n type: random:Password\n properties:\n length: 16\n lower: false\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\nThen importing the resource using `terraform import random_password.password securepassword`,\nwould result in the triggering of a replacement (i.e., destroy-create) during the next\n`pulumi up`.\n\n### Avoiding Replacement\n\nIf the resource were imported using `terraform import random_password.password securepassword`,\nreplacement could be avoided by using:\n\n1. Attribute values that match the imported ID and defaults:\n\n \u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as random from \"@pulumi/random\";\n\nconst password = new random.Password(\"password\", {\n length: 14,\n lower: true,\n});\n```\n```python\nimport pulumi\nimport pulumi_random as random\n\npassword = random.Password(\"password\",\n length=float(14),\n lower=True)\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var password = new Random.Password(\"password\", new()\n {\n Length = 14,\n Lower = true,\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := random.NewPassword(ctx, \"password\", \u0026random.PasswordArgs{\n\t\t\tLength: pulumi.Float64(14),\n\t\t\tLower: pulumi.Bool(true),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_password\" \"password\" {\n length = 14\n lower = true\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.Password;\nimport com.pulumi.random.PasswordArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var password = new Password(\"password\", PasswordArgs.builder()\n .length(14.0)\n .lower(true)\n .build());\n\n }\n}\n```\n```yaml\nresources:\n password:\n type: random:Password\n properties:\n length: 14\n lower: true\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\n\n2. Attribute values that match the imported ID and omit the attributes with defaults:\n\n \u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as random from \"@pulumi/random\";\n\nconst password = new random.Password(\"password\", {length: 14});\n```\n```python\nimport pulumi\nimport pulumi_random as random\n\npassword = random.Password(\"password\", length=float(14))\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var password = new Random.Password(\"password\", new()\n {\n Length = 14,\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := random.NewPassword(ctx, \"password\", \u0026random.PasswordArgs{\n\t\t\tLength: pulumi.Float64(14),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_password\" \"password\" {\n length = 14\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.Password;\nimport com.pulumi.random.PasswordArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var password = new Password(\"password\", PasswordArgs.builder()\n .length(14.0)\n .build());\n\n }\n}\n```\n```yaml\nresources:\n password:\n type: random:Password\n properties:\n length: 14\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\n\n3. \u003cspan pulumi-lang-nodejs=\"`ignoreChanges`\" pulumi-lang-dotnet=\"`IgnoreChanges`\" pulumi-lang-go=\"`ignoreChanges`\" pulumi-lang-python=\"`ignore_changes`\" pulumi-lang-yaml=\"`ignoreChanges`\" pulumi-lang-java=\"`ignoreChanges`\" pulumi-lang-hcl=\"`ignore_changes`\"\u003e`ignoreChanges`\u003c/span\u003e specifying the attributes to ignore:\n\n \u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as random from \"@pulumi/random\";\n\nconst password = new random.Password(\"password\", {\n length: 16,\n lower: false,\n});\n```\n```python\nimport pulumi\nimport pulumi_random as random\n\npassword = random.Password(\"password\",\n length=float(16),\n lower=False)\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var password = new Random.Password(\"password\", new()\n {\n Length = 16,\n Lower = false,\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := random.NewPassword(ctx, \"password\", \u0026random.PasswordArgs{\n\t\t\tLength: pulumi.Float64(16),\n\t\t\tLower: pulumi.Bool(false),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_password\" \"password\" {\n length = 16\n lower = false\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.Password;\nimport com.pulumi.random.PasswordArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var password = new Password(\"password\", PasswordArgs.builder()\n .length(16.0)\n .lower(false)\n .build());\n\n }\n}\n```\n```yaml\nresources:\n password:\n type: random:Password\n properties:\n length: 16\n lower: false\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\n **NOTE** \u003cspan pulumi-lang-nodejs=\"`ignoreChanges`\" pulumi-lang-dotnet=\"`IgnoreChanges`\" pulumi-lang-go=\"`ignoreChanges`\" pulumi-lang-python=\"`ignore_changes`\" pulumi-lang-yaml=\"`ignoreChanges`\" pulumi-lang-java=\"`ignoreChanges`\" pulumi-lang-hcl=\"`ignore_changes`\"\u003e`ignoreChanges`\u003c/span\u003e is only required until the resource is recreated after import,\n after which it will use the configuration values specified.\n\n", + "description": "Identical to\u003cspan pulumi-lang-nodejs=\" random.String \" pulumi-lang-dotnet=\" random.String \" pulumi-lang-go=\" String \" pulumi-lang-python=\" String \" pulumi-lang-yaml=\" random.String \" pulumi-lang-java=\" random.String \" pulumi-lang-hcl=\" random_string \"\u003e random.String \u003c/span\u003ewith the exception that the result is treated as sensitive and, thus, _not_ displayed in console output. Read more about sensitive data handling in the Terraform documentation.\n\nThis resource *does* use a cryptographic random number generator.\n\n## Example Usage\n\n\u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as aws from \"@pulumi/aws\";\nimport * as random from \"@pulumi/random\";\n\nconst password = new random.Password(\"password\", {\n length: 16,\n special: true,\n overrideSpecial: \"!#$%\u0026*()-_=+[]{}\u003c\u003e:?\",\n});\nconst example = new aws.rds.Instance(\"example\", {\n instanceClass: aws.rds.InstanceType.T3_Micro,\n allocatedStorage: 64,\n engine: \"mysql\",\n username: \"someone\",\n password: password.result,\n});\n```\n```python\nimport pulumi\nimport pulumi_aws as aws\nimport pulumi_random as random\n\npassword = random.Password(\"password\",\n length=float(16),\n special=True,\n override_special=\"!#$%\u0026*()-_=+[]{}\u003c\u003e:?\")\nexample = aws.rds.Instance(\"example\",\n instance_class=aws.rds.InstanceType.T3_MICRO,\n allocated_storage=64,\n engine=\"mysql\",\n username=\"someone\",\n password=password.result)\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Aws = Pulumi.Aws;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var password = new Random.Password(\"password\", new()\n {\n Length = 16,\n Special = true,\n OverrideSpecial = \"!#$%\u0026*()-_=+[]{}\u003c\u003e:?\",\n });\n\n var example = new Aws.Rds.Instance(\"example\", new()\n {\n InstanceClass = Aws.Rds.InstanceType.T3_Micro,\n AllocatedStorage = 64,\n Engine = \"mysql\",\n Username = \"someone\",\n Password = password.Result,\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/rds\"\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\tpassword, err := random.NewPassword(ctx, \"password\", \u0026random.PasswordArgs{\n\t\t\tLength: pulumi.Float64(16),\n\t\t\tSpecial: pulumi.Bool(true),\n\t\t\tOverrideSpecial: pulumi.String(\"!#$%\u0026*()-_=+[]{}\u003c\u003e:?\"),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = rds.NewInstance(ctx, \"example\", \u0026rds.InstanceArgs{\n\t\t\tInstanceClass: pulumi.String(rds.InstanceType_T3_Micro),\n\t\t\tAllocatedStorage: pulumi.Int(64),\n\t\t\tEngine: pulumi.String(\"mysql\"),\n\t\t\tUsername: pulumi.String(\"someone\"),\n\t\t\tPassword: password.Result,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n aws = {\n source = \"pulumi/aws\"\n }\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_password\" \"password\" {\n length = 16\n special = true\n override_special = \"!#$%\u0026*()-_=+[]{}\u003c\u003e:?\"\n}\nresource \"aws_rds_instance\" \"example\" {\n instance_class = \"db.t3.micro\"\n allocated_storage = 64\n engine = \"mysql\"\n username = \"someone\"\n password = random_password.password.result\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.Password;\nimport com.pulumi.random.PasswordArgs;\nimport com.pulumi.aws.rds.Instance;\nimport com.pulumi.aws.rds.InstanceArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var password = new Password(\"password\", PasswordArgs.builder()\n .length(16.0)\n .special(true)\n .overrideSpecial(\"!#$%\u0026*()-_=+[]{}\u003c\u003e:?\")\n .build());\n\n var example = new Instance(\"example\", InstanceArgs.builder()\n .instanceClass(\"db.t3.micro\")\n .allocatedStorage(64)\n .engine(\"mysql\")\n .username(\"someone\")\n .password(password.result())\n .build());\n\n }\n}\n```\n```yaml\nresources:\n password:\n type: random:Password\n properties:\n length: 16\n special: true\n overrideSpecial: '!#$%\u0026*()-_=+[]{}\u003c\u003e:?'\n example:\n type: aws:rds:Instance\n properties:\n instanceClass: db.t3.micro\n allocatedStorage: 64\n engine: mysql\n username: someone\n password: ${password.result}\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\n## Import\n\n```sh\n$ pulumi import random:index/password:Password password securepassword\n```\n\n### Limitations of Import\n\nAny attribute values that are specified within Terraform config will be\nignored during import and all attributes that have defaults defined within\nthe schema will have the default assigned.\n\nFor instance, using the following config during import:\n\u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as random from \"@pulumi/random\";\n\nconst password = new random.Password(\"password\", {\n length: 16,\n lower: false,\n});\n```\n```python\nimport pulumi\nimport pulumi_random as random\n\npassword = random.Password(\"password\",\n length=float(16),\n lower=False)\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var password = new Random.Password(\"password\", new()\n {\n Length = 16,\n Lower = false,\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := random.NewPassword(ctx, \"password\", \u0026random.PasswordArgs{\n\t\t\tLength: pulumi.Float64(16),\n\t\t\tLower: pulumi.Bool(false),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_password\" \"password\" {\n length = 16\n lower = false\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.Password;\nimport com.pulumi.random.PasswordArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var password = new Password(\"password\", PasswordArgs.builder()\n .length(16.0)\n .lower(false)\n .build());\n\n }\n}\n```\n```yaml\nresources:\n password:\n type: random:Password\n properties:\n length: 16\n lower: false\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\nThen importing the resource using `pulumi import random:index/password:Password password securepassword`,\nwould result in the triggering of a replacement (i.e., destroy-create) during the next\n`pulumi up`.\n\n### Avoiding Replacement\n\nIf the resource were imported using `pulumi import random:index/password:Password password securepassword`,\nreplacement could be avoided by using:\n\n1. Attribute values that match the imported ID and defaults:\n\n \u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as random from \"@pulumi/random\";\n\nconst password = new random.Password(\"password\", {\n length: 14,\n lower: true,\n});\n```\n```python\nimport pulumi\nimport pulumi_random as random\n\npassword = random.Password(\"password\",\n length=float(14),\n lower=True)\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var password = new Random.Password(\"password\", new()\n {\n Length = 14,\n Lower = true,\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := random.NewPassword(ctx, \"password\", \u0026random.PasswordArgs{\n\t\t\tLength: pulumi.Float64(14),\n\t\t\tLower: pulumi.Bool(true),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_password\" \"password\" {\n length = 14\n lower = true\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.Password;\nimport com.pulumi.random.PasswordArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var password = new Password(\"password\", PasswordArgs.builder()\n .length(14.0)\n .lower(true)\n .build());\n\n }\n}\n```\n```yaml\nresources:\n password:\n type: random:Password\n properties:\n length: 14\n lower: true\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\n\n2. Attribute values that match the imported ID and omit the attributes with defaults:\n\n \u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as random from \"@pulumi/random\";\n\nconst password = new random.Password(\"password\", {length: 14});\n```\n```python\nimport pulumi\nimport pulumi_random as random\n\npassword = random.Password(\"password\", length=float(14))\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var password = new Random.Password(\"password\", new()\n {\n Length = 14,\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := random.NewPassword(ctx, \"password\", \u0026random.PasswordArgs{\n\t\t\tLength: pulumi.Float64(14),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_password\" \"password\" {\n length = 14\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.Password;\nimport com.pulumi.random.PasswordArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var password = new Password(\"password\", PasswordArgs.builder()\n .length(14.0)\n .build());\n\n }\n}\n```\n```yaml\nresources:\n password:\n type: random:Password\n properties:\n length: 14\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\n\n3. \u003cspan pulumi-lang-nodejs=\"`ignoreChanges`\" pulumi-lang-dotnet=\"`IgnoreChanges`\" pulumi-lang-go=\"`ignoreChanges`\" pulumi-lang-python=\"`ignore_changes`\" pulumi-lang-yaml=\"`ignoreChanges`\" pulumi-lang-java=\"`ignoreChanges`\" pulumi-lang-hcl=\"`ignore_changes`\"\u003e`ignoreChanges`\u003c/span\u003e specifying the attributes to ignore:\n\n \u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as random from \"@pulumi/random\";\n\nconst password = new random.Password(\"password\", {\n length: 16,\n lower: false,\n});\n```\n```python\nimport pulumi\nimport pulumi_random as random\n\npassword = random.Password(\"password\",\n length=float(16),\n lower=False)\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var password = new Random.Password(\"password\", new()\n {\n Length = 16,\n Lower = false,\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := random.NewPassword(ctx, \"password\", \u0026random.PasswordArgs{\n\t\t\tLength: pulumi.Float64(16),\n\t\t\tLower: pulumi.Bool(false),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_password\" \"password\" {\n length = 16\n lower = false\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.Password;\nimport com.pulumi.random.PasswordArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var password = new Password(\"password\", PasswordArgs.builder()\n .length(16.0)\n .lower(false)\n .build());\n\n }\n}\n```\n```yaml\nresources:\n password:\n type: random:Password\n properties:\n length: 16\n lower: false\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\n **NOTE** \u003cspan pulumi-lang-nodejs=\"`ignoreChanges`\" pulumi-lang-dotnet=\"`IgnoreChanges`\" pulumi-lang-go=\"`ignoreChanges`\" pulumi-lang-python=\"`ignore_changes`\" pulumi-lang-yaml=\"`ignoreChanges`\" pulumi-lang-java=\"`ignoreChanges`\" pulumi-lang-hcl=\"`ignore_changes`\"\u003e`ignoreChanges`\u003c/span\u003e is only required until the resource is recreated after import,\n after which it will use the configuration values specified.\n\n", "properties": { "bcryptHash": { "type": "string", @@ -694,7 +694,7 @@ } }, "random:index/string:String": { - "description": "The resource \u003cspan pulumi-lang-nodejs=\"`random.String`\" pulumi-lang-dotnet=\"`random.String`\" pulumi-lang-go=\"`String`\" pulumi-lang-python=\"`String`\" pulumi-lang-yaml=\"`random.String`\" pulumi-lang-java=\"`random.String`\" pulumi-lang-hcl=\"`random_string`\"\u003e`random.String`\u003c/span\u003e generates a random permutation of alphanumeric characters and optionally special characters.\n\nThis resource *does* use a cryptographic random number generator.\n\nHistorically this resource's intended usage has been ambiguous as the original example used it in a password. For backwards compatibility it will continue to exist. For unique ids please use random_id, for sensitive random values please use random_password.\n\n## Example Usage\n\n\u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as random from \"@pulumi/random\";\n\nconst random2 = new random.String(\"random\", {\n length: 16,\n special: true,\n overrideSpecial: \"/@£$\",\n});\n```\n```python\nimport pulumi\nimport pulumi_random as random\n\nrandom_1 = random.String(\"random\",\n length=float(16),\n special=True,\n override_special=\"/@£$\")\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var random = new Random.String(\"random\", new()\n {\n Length = 16,\n Special = true,\n OverrideSpecial = \"/@£$\",\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := random.NewString(ctx, \"random\", \u0026random.StringArgs{\n\t\t\tLength: pulumi.Float64(16),\n\t\t\tSpecial: pulumi.Bool(true),\n\t\t\tOverrideSpecial: pulumi.String(\"/@£$\"),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_string\" \"random\" {\n length = 16\n special = true\n override_special = \"/@£$\"\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.String;\nimport com.pulumi.random.StringArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(java.lang.String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var random = new String(\"random\", StringArgs.builder()\n .length(16.0)\n .special(true)\n .overrideSpecial(\"/@£$\")\n .build());\n\n }\n}\n```\n```yaml\nresources:\n random:\n type: random:String\n properties:\n length: 16\n special: true\n overrideSpecial: /@£$\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\n## Import\n\n```sh\n$ pulumi import random:index/string:String test test\n```\n\n### Limitations of Import\n\nAny attribute values that are specified within Terraform config will be\nignored during import and all attributes that have defaults defined within\nthe schema will have the default assigned.\n\nFor instance, using the following config during import:\n\u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as random from \"@pulumi/random\";\n\nconst test = new random.String(\"test\", {\n length: 16,\n lower: false,\n});\n```\n```python\nimport pulumi\nimport pulumi_random as random\n\ntest = random.String(\"test\",\n length=float(16),\n lower=False)\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var test = new Random.String(\"test\", new()\n {\n Length = 16,\n Lower = false,\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := random.NewString(ctx, \"test\", \u0026random.StringArgs{\n\t\t\tLength: pulumi.Float64(16),\n\t\t\tLower: pulumi.Bool(false),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_string\" \"test\" {\n length = 16\n lower = false\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.String;\nimport com.pulumi.random.StringArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(java.lang.String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var test = new String(\"test\", StringArgs.builder()\n .length(16.0)\n .lower(false)\n .build());\n\n }\n}\n```\n```yaml\nresources:\n test:\n type: random:String\n properties:\n length: 16\n lower: false\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\nThen importing the resource using `terraform import random_string.test test`,\nwould result in the triggering of a replacement (i.e., destroy-create) during\nthe next `pulumi up`.\n\n### Avoiding Replacement\n\nIf the resource were imported using `terraform import random_string.test test`,\nreplacement can be avoided by using:\n\n1. Attribute values that match the imported ID and defaults:\n \u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as random from \"@pulumi/random\";\n\nconst test = new random.String(\"test\", {\n length: 4,\n lower: true,\n});\n```\n```python\nimport pulumi\nimport pulumi_random as random\n\ntest = random.String(\"test\",\n length=float(4),\n lower=True)\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var test = new Random.String(\"test\", new()\n {\n Length = 4,\n Lower = true,\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := random.NewString(ctx, \"test\", \u0026random.StringArgs{\n\t\t\tLength: pulumi.Float64(4),\n\t\t\tLower: pulumi.Bool(true),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_string\" \"test\" {\n length = 4\n lower = true\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.String;\nimport com.pulumi.random.StringArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(java.lang.String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var test = new String(\"test\", StringArgs.builder()\n .length(4.0)\n .lower(true)\n .build());\n\n }\n}\n```\n```yaml\nresources:\n test:\n type: random:String\n properties:\n length: 4\n lower: true\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\n2. Attribute values that match the imported ID and omit the attributes with defaults:\n \u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as random from \"@pulumi/random\";\n\nconst test = new random.String(\"test\", {length: 4});\n```\n```python\nimport pulumi\nimport pulumi_random as random\n\ntest = random.String(\"test\", length=float(4))\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var test = new Random.String(\"test\", new()\n {\n Length = 4,\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := random.NewString(ctx, \"test\", \u0026random.StringArgs{\n\t\t\tLength: pulumi.Float64(4),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_string\" \"test\" {\n length = 4\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.String;\nimport com.pulumi.random.StringArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(java.lang.String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var test = new String(\"test\", StringArgs.builder()\n .length(4.0)\n .build());\n\n }\n}\n```\n```yaml\nresources:\n test:\n type: random:String\n properties:\n length: 4\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\n3. \u003cspan pulumi-lang-nodejs=\"`ignoreChanges`\" pulumi-lang-dotnet=\"`IgnoreChanges`\" pulumi-lang-go=\"`ignoreChanges`\" pulumi-lang-python=\"`ignore_changes`\" pulumi-lang-yaml=\"`ignoreChanges`\" pulumi-lang-java=\"`ignoreChanges`\" pulumi-lang-hcl=\"`ignore_changes`\"\u003e`ignoreChanges`\u003c/span\u003e specifying the attributes to ignore:\n \u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as random from \"@pulumi/random\";\n\nconst test = new random.String(\"test\", {\n length: 16,\n lower: false,\n});\n```\n```python\nimport pulumi\nimport pulumi_random as random\n\ntest = random.String(\"test\",\n length=float(16),\n lower=False)\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var test = new Random.String(\"test\", new()\n {\n Length = 16,\n Lower = false,\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := random.NewString(ctx, \"test\", \u0026random.StringArgs{\n\t\t\tLength: pulumi.Float64(16),\n\t\t\tLower: pulumi.Bool(false),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_string\" \"test\" {\n length = 16\n lower = false\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.String;\nimport com.pulumi.random.StringArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(java.lang.String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var test = new String(\"test\", StringArgs.builder()\n .length(16.0)\n .lower(false)\n .build());\n\n }\n}\n```\n```yaml\nresources:\n test:\n type: random:String\n properties:\n length: 16\n lower: false\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\n **NOTE** \u003cspan pulumi-lang-nodejs=\"`ignoreChanges`\" pulumi-lang-dotnet=\"`IgnoreChanges`\" pulumi-lang-go=\"`ignoreChanges`\" pulumi-lang-python=\"`ignore_changes`\" pulumi-lang-yaml=\"`ignoreChanges`\" pulumi-lang-java=\"`ignoreChanges`\" pulumi-lang-hcl=\"`ignore_changes`\"\u003e`ignoreChanges`\u003c/span\u003e is only required until the resource is recreated after import,\n after which it will use the configuration values specified.\n\n", + "description": "The resource \u003cspan pulumi-lang-nodejs=\"`random.String`\" pulumi-lang-dotnet=\"`random.String`\" pulumi-lang-go=\"`String`\" pulumi-lang-python=\"`String`\" pulumi-lang-yaml=\"`random.String`\" pulumi-lang-java=\"`random.String`\" pulumi-lang-hcl=\"`random_string`\"\u003e`random.String`\u003c/span\u003e generates a random permutation of alphanumeric characters and optionally special characters.\n\nThis resource *does* use a cryptographic random number generator.\n\nHistorically this resource's intended usage has been ambiguous as the original example used it in a password. For backwards compatibility it will continue to exist. For unique ids please use random_id, for sensitive random values please use random_password.\n\n## Example Usage\n\n\u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as random from \"@pulumi/random\";\n\nconst random2 = new random.String(\"random\", {\n length: 16,\n special: true,\n overrideSpecial: \"/@£$\",\n});\n```\n```python\nimport pulumi\nimport pulumi_random as random\n\nrandom_1 = random.String(\"random\",\n length=float(16),\n special=True,\n override_special=\"/@£$\")\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var random = new Random.String(\"random\", new()\n {\n Length = 16,\n Special = true,\n OverrideSpecial = \"/@£$\",\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := random.NewString(ctx, \"random\", \u0026random.StringArgs{\n\t\t\tLength: pulumi.Float64(16),\n\t\t\tSpecial: pulumi.Bool(true),\n\t\t\tOverrideSpecial: pulumi.String(\"/@£$\"),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_string\" \"random\" {\n length = 16\n special = true\n override_special = \"/@£$\"\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.String;\nimport com.pulumi.random.StringArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(java.lang.String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var random = new String(\"random\", StringArgs.builder()\n .length(16.0)\n .special(true)\n .overrideSpecial(\"/@£$\")\n .build());\n\n }\n}\n```\n```yaml\nresources:\n random:\n type: random:String\n properties:\n length: 16\n special: true\n overrideSpecial: /@£$\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\n## Import\n\n```sh\n$ pulumi import random:index/string:String test test\n```\n\n### Limitations of Import\n\nAny attribute values that are specified within Terraform config will be\nignored during import and all attributes that have defaults defined within\nthe schema will have the default assigned.\n\nFor instance, using the following config during import:\n\u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as random from \"@pulumi/random\";\n\nconst test = new random.String(\"test\", {\n length: 16,\n lower: false,\n});\n```\n```python\nimport pulumi\nimport pulumi_random as random\n\ntest = random.String(\"test\",\n length=float(16),\n lower=False)\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var test = new Random.String(\"test\", new()\n {\n Length = 16,\n Lower = false,\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := random.NewString(ctx, \"test\", \u0026random.StringArgs{\n\t\t\tLength: pulumi.Float64(16),\n\t\t\tLower: pulumi.Bool(false),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_string\" \"test\" {\n length = 16\n lower = false\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.String;\nimport com.pulumi.random.StringArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(java.lang.String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var test = new String(\"test\", StringArgs.builder()\n .length(16.0)\n .lower(false)\n .build());\n\n }\n}\n```\n```yaml\nresources:\n test:\n type: random:String\n properties:\n length: 16\n lower: false\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\nThen importing the resource using `pulumi import random:index/string:String test test`,\nwould result in the triggering of a replacement (i.e., destroy-create) during\nthe next `pulumi up`.\n\n### Avoiding Replacement\n\nIf the resource were imported using `pulumi import random:index/string:String test test`,\nreplacement can be avoided by using:\n\n1. Attribute values that match the imported ID and defaults:\n \u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as random from \"@pulumi/random\";\n\nconst test = new random.String(\"test\", {\n length: 4,\n lower: true,\n});\n```\n```python\nimport pulumi\nimport pulumi_random as random\n\ntest = random.String(\"test\",\n length=float(4),\n lower=True)\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var test = new Random.String(\"test\", new()\n {\n Length = 4,\n Lower = true,\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := random.NewString(ctx, \"test\", \u0026random.StringArgs{\n\t\t\tLength: pulumi.Float64(4),\n\t\t\tLower: pulumi.Bool(true),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_string\" \"test\" {\n length = 4\n lower = true\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.String;\nimport com.pulumi.random.StringArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(java.lang.String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var test = new String(\"test\", StringArgs.builder()\n .length(4.0)\n .lower(true)\n .build());\n\n }\n}\n```\n```yaml\nresources:\n test:\n type: random:String\n properties:\n length: 4\n lower: true\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\n2. Attribute values that match the imported ID and omit the attributes with defaults:\n \u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as random from \"@pulumi/random\";\n\nconst test = new random.String(\"test\", {length: 4});\n```\n```python\nimport pulumi\nimport pulumi_random as random\n\ntest = random.String(\"test\", length=float(4))\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var test = new Random.String(\"test\", new()\n {\n Length = 4,\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := random.NewString(ctx, \"test\", \u0026random.StringArgs{\n\t\t\tLength: pulumi.Float64(4),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_string\" \"test\" {\n length = 4\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.String;\nimport com.pulumi.random.StringArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(java.lang.String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var test = new String(\"test\", StringArgs.builder()\n .length(4.0)\n .build());\n\n }\n}\n```\n```yaml\nresources:\n test:\n type: random:String\n properties:\n length: 4\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\n3. \u003cspan pulumi-lang-nodejs=\"`ignoreChanges`\" pulumi-lang-dotnet=\"`IgnoreChanges`\" pulumi-lang-go=\"`ignoreChanges`\" pulumi-lang-python=\"`ignore_changes`\" pulumi-lang-yaml=\"`ignoreChanges`\" pulumi-lang-java=\"`ignoreChanges`\" pulumi-lang-hcl=\"`ignore_changes`\"\u003e`ignoreChanges`\u003c/span\u003e specifying the attributes to ignore:\n \u003c!--Start PulumiCodeChooser --\u003e\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as random from \"@pulumi/random\";\n\nconst test = new random.String(\"test\", {\n length: 16,\n lower: false,\n});\n```\n```python\nimport pulumi\nimport pulumi_random as random\n\ntest = random.String(\"test\",\n length=float(16),\n lower=False)\n```\n```csharp\nusing System.Collections.Generic;\nusing System.Linq;\nusing Pulumi;\nusing Random = Pulumi.Random;\n\nreturn await Deployment.RunAsync(() =\u003e \n{\n var test = new Random.String(\"test\", new()\n {\n Length = 16,\n Lower = false,\n });\n\n});\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-terraform-provider/sdks/go/random/v3/random\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\t_, err := random.NewString(ctx, \"test\", \u0026random.StringArgs{\n\t\t\tLength: pulumi.Float64(16),\n\t\t\tLower: pulumi.Bool(false),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n```\n```hcl\npulumi {\n required_providers {\n random = {\n source = \"pulumi/random\"\n }\n }\n}\n\nresource \"random_string\" \"test\" {\n length = 16\n lower = false\n}\n```\n```java\npackage generated_program;\n\nimport com.pulumi.Context;\nimport com.pulumi.Pulumi;\nimport com.pulumi.core.Output;\nimport com.pulumi.random.String;\nimport com.pulumi.random.StringArgs;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class App {\n public static void main(java.lang.String[] args) {\n Pulumi.run(App::stack);\n }\n\n public static void stack(Context ctx) {\n var test = new String(\"test\", StringArgs.builder()\n .length(16.0)\n .lower(false)\n .build());\n\n }\n}\n```\n```yaml\nresources:\n test:\n type: random:String\n properties:\n length: 16\n lower: false\n```\n\u003c!--End PulumiCodeChooser --\u003e\n\n **NOTE** \u003cspan pulumi-lang-nodejs=\"`ignoreChanges`\" pulumi-lang-dotnet=\"`IgnoreChanges`\" pulumi-lang-go=\"`ignoreChanges`\" pulumi-lang-python=\"`ignore_changes`\" pulumi-lang-yaml=\"`ignoreChanges`\" pulumi-lang-java=\"`ignoreChanges`\" pulumi-lang-hcl=\"`ignore_changes`\"\u003e`ignoreChanges`\u003c/span\u003e is only required until the resource is recreated after import,\n after which it will use the configuration values specified.\n\n", "properties": { "keepers": { "type": "object",