Repository: pulumi/pulumi-terraform-bridge
Pull request #457 (2022-02-22 18:14:18)

Add a language PCL that produces examples as PCL
Add an additional language target: pcl. We use this to extract pcl from provider examples. The use case here is testing programgen against a larger corpus of examples. 

github-actions[bot]: Diff for [pulumi-random](https://github.com/pulumi-bot/pulumi-random/compare/984f697..3194239) with merge commit 0d1d5584e8385dab85b2a9882188e223aa713238
github-actions[bot]: Diff for [pulumi-azuread](https://github.com/pulumi-bot/pulumi-azuread/compare/67c5d8c..a421aff) with merge commit 0d1d5584e8385dab85b2a9882188e223aa713238
github-actions[bot]: Diff for [pulumi-gcp](https://github.com/pulumi-bot/pulumi-gcp/compare/826b1fded..be96273e0) with merge commit 0d1d5584e8385dab85b2a9882188e223aa713238
github-actions[bot]: Diff for [pulumi-azure](https://github.com/pulumi-bot/pulumi-azure/compare/523f8aca8..77ac59d16) with merge commit 0d1d5584e8385dab85b2a9882188e223aa713238
github-actions[bot]: Diff for [pulumi-aws](https://github.com/pulumi-bot/pulumi-aws/compare/04bd36117..2edcd2ca6) with merge commit 0d1d5584e8385dab85b2a9882188e223aa713238

Patch:
diff --git a/go.mod b/go.mod
index 5c85d0f3..6b6866e5 100644
--- a/go.mod
+++ b/go.mod
@@ -17,7 +17,7 @@ require (
 	github.com/hashicorp/go-multierror v1.1.1
 	github.com/hashicorp/go-plugin v1.4.1
 	github.com/hashicorp/go-uuid v1.0.2
-	github.com/hashicorp/go-version v1.3.0
+	github.com/hashicorp/go-version v1.4.0
 	github.com/hashicorp/hcl v1.0.0
 	github.com/hashicorp/hcl/v2 v2.3.0
 	github.com/hashicorp/hil v0.0.0-20190212132231-97b3a9cdfa93
@@ -33,8 +33,8 @@ require (
 	github.com/mitchellh/mapstructure v1.4.1
 	github.com/mitchellh/reflectwalk v1.0.2
 	github.com/pkg/errors v0.9.1
-	github.com/pulumi/pulumi/pkg/v3 v3.24.1
-	github.com/pulumi/pulumi/sdk/v3 v3.24.1
+	github.com/pulumi/pulumi/pkg/v3 v3.25.1-0.20220226222616-301efa60653c
+	github.com/pulumi/pulumi/sdk/v3 v3.25.0
 	github.com/pulumi/terraform-diff-reader v0.0.0-20201211191010-ad4715e9285e
 	github.com/russross/blackfriday/v2 v2.1.0
 	github.com/spf13/afero v1.2.2
diff --git a/go.sum b/go.sum
index 60e088bd..b6643a25 100644
--- a/go.sum
+++ b/go.sum
@@ -437,8 +437,9 @@ github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2I
 github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
-github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw=
 github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4=
+github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
@@ -490,6 +491,8 @@ github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
 github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
+github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ijc/Gotty v0.0.0-20170406111628-a8b993ba6abd h1:anPrsicrIi2ColgWTVPk+TrN42hJIWlfPHSBP9S0ZkM=
@@ -683,11 +686,10 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
-github.com/pulumi/pulumi/pkg/v3 v3.24.1 h1:5Jqu/eR4P8O0ST7ZA9VqxX7Vq23g5y3dp2K0Egiy8w0=
-github.com/pulumi/pulumi/pkg/v3 v3.24.1/go.mod h1:3MYj+8Rmv0mWYWXEQ6z8TeoRxS7vnqow9o/c/tp6IHI=
-github.com/pulumi/pulumi/sdk/v3 v3.24.0/go.mod h1:WHOQB00iuHZyXhwrymxpKXhpOahSguJIpRjVokmM11w=
-github.com/pulumi/pulumi/sdk/v3 v3.24.1 h1:Ywaih9y/zBfS8s4w6BtcsBF/rHQpGbbZ82oXwEVUfVI=
-github.com/pulumi/pulumi/sdk/v3 v3.24.1/go.mod h1:WHOQB00iuHZyXhwrymxpKXhpOahSguJIpRjVokmM11w=
+github.com/pulumi/pulumi/pkg/v3 v3.25.1-0.20220226222616-301efa60653c h1:zkvrK+mXnjlJSlVAnGtATf2HrhEzAenvlovCseIP1jU=
+github.com/pulumi/pulumi/pkg/v3 v3.25.1-0.20220226222616-301efa60653c/go.mod h1:O4MS08knSlZrfbUjoHw2C7mZJlspNuHRDxovnm3Bk8g=
+github.com/pulumi/pulumi/sdk/v3 v3.25.0 h1:ZLO5sXjtEcPJKveX8cL7YzNIvGM+/lxQ6uhgLGkNl2w=
+github.com/pulumi/pulumi/sdk/v3 v3.25.0/go.mod h1:VsxW+TGv2VBLe/MeqsAr9r0zKzK/gbAhFT9QxYr24cY=
 github.com/pulumi/terraform-diff-reader v0.0.0-20201211191010-ad4715e9285e h1:Dik4Qe/+xguB8JagPyXNlbOnRiXGmq/PSPQTGunYnTk=
 github.com/pulumi/terraform-diff-reader v0.0.0-20201211191010-ad4715e9285e/go.mod h1:sZ9FUzGO+yM41hsQHs/yIcj/Y993qMdBxBU5mpDmAfQ=
 github.com/pulumi/terraform-plugin-sdk/v2 v2.0.0-20211230170131-3a7c83bfab87 h1:Reqyb/CbcDwThvBRzA62H7cvuCqgTJuGNt+F6mnmXJ4=
diff --git a/pkg/tf2pulumi/convert/tf11.go b/pkg/tf2pulumi/convert/tf11.go
index d42712d1..acc998ea 100644
--- a/pkg/tf2pulumi/convert/tf11.go
+++ b/pkg/tf2pulumi/convert/tf11.go
@@ -175,8 +175,12 @@ func newGenerator(w io.Writer, projectName string, opts Options) (gen.Generator,
 	case LanguagePython:
 		return python.New(projectName, w), "__main__.py", nil
 	default:
+		validLanguages := make([]string, len(ValidLanguages))
+		for i, l := range ValidLanguages {
+			validLanguages[i] = string(l)
+		}
 		return nil, "", errors.Errorf("invalid language '%s', expected one of %s",
-			opts.TargetLanguage, strings.Join(ValidLanguages[:], ", "))
+			opts.TargetLanguage, strings.Join(validLanguages, ", "))
 	}
 }
 
diff --git a/pkg/tfbridge/schema.go b/pkg/tfbridge/schema.go
index 7b553c1c..c3f4d9e0 100644
--- a/pkg/tfbridge/schema.go
+++ b/pkg/tfbridge/schema.go
@@ -25,7 +25,6 @@ import (
 
 	"github.com/golang/glog"
 	pbstruct "github.com/golang/protobuf/ptypes/struct"
-	"github.com/mitchellh/copystructure"
 	"github.com/pkg/errors"
 	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
 	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
@@ -1100,43 +1099,104 @@ func extractInputs(oldInput, newState resource.PropertyValue, tfs shim.Schema, p
 	}
 }
 
-func addDefaultAnnotations(newInput resource.PropertyValue) {
-	switch {
-	case newInput.IsArray():
-		newArray := newInput.ArrayValue()
-		for i := range newArray {
-			addDefaultAnnotations(newArray[i])
+func getDefaultValue(tfs shim.Schema, ps *SchemaInfo) interface{} {
+	if dv, _ := tfs.DefaultValue(); dv != nil {
+		return dv
+	}
+	// TODO: We should inspect SchemaInfo.Default for the default value as well
+	// if ps != nil {
+	// 	return ps.Default
+	// }
+	return nil
+}
+
+func isDefaultOrZeroValue(tfs shim.Schema, ps *SchemaInfo, v resource.PropertyValue) bool {
+	if dv := getDefaultValue(tfs, ps); dv != nil {
+		// For numbers PropertyValue.V will always be a float64, but Schema.DefaultValue() from terraform might be float64 or int.
+
+		if i, ok := dv.(int); ok {
+			dv = float64(i)
 		}
-	case newInput.IsObject():
-		newMap := newInput.ObjectValue()
-		for _, newValue := range newMap {
-			addDefaultAnnotations(newValue)
+		return dv == v.V
+	}
+
+	switch {
+	case v.IsNull():
+		return true
+	case v.IsBool():
+		return v.BoolValue() == false
+	case v.IsNumber():
+		return v.NumberValue() == 0
+	case v.IsString():
+		return v.StringValue() == ""
+	case v.IsArray():
+		return len(v.ArrayValue()) == 0
+	case v.IsObject():
+		obj := v.ObjectValue()
+		switch len(obj) {
+		case 0:
+			return true
+		case 1:
+			_, ok := obj[defaultsKey]
+			return ok
+		default:
+			return false
 		}
-		newMap[defaultsKey] = resource.NewArrayProperty([]resource.PropertyValue{})
+	case v.IsSecret():
+		secret := v.SecretValue()
+		return isDefaultOrZeroValue(tfs, ps, secret.Element)
+	default:
+		return false
 	}
 }
 
-func extractSchemaInputs(state resource.PropertyValue, tfs shim.SchemaMap,
-	ps map[string]*SchemaInfo) (resource.PropertyValue, error) {
-	inputs := make(resource.PropertyMap)
-	for name, value := range state.ObjectValue() {
-		// If this property is not an input, ignore it.
-		_, sch, _ := getInfoFromPulumiName(name, tfs, ps, false)
-		if sch == nil || (!sch.Optional() && !sch.Required()) {
-			continue
+func extractSchemaInputs(state resource.PropertyValue, tfs shim.Schema, ps *SchemaInfo,
+	rawNames bool) resource.PropertyValue {
+
+	switch {
+	case state.IsArray():
+		etfs, eps := elemSchemas(tfs, ps)
+
+		a := state.ArrayValue()
+		v := make([]resource.PropertyValue, len(a))
+		for i := range a {
+			v[i] = extractSchemaInputs(a[i], etfs, eps, rawNames || useRawNames(tfs))
+		}
+		return resource.NewArrayProperty(v)
+	case state.IsObject():
+		var tfflds shim.SchemaMap
+		if tfs != nil {
+			if res, isres := tfs.Elem().(shim.Resource); isres {
+				tfflds = res.Schema()
+			}
+		}
+		var psflds map[string]*SchemaInfo
+		if ps != nil {
+			psflds = ps.Fields
 		}
+		isMap := tfflds == nil
+
+		obj := state.ObjectValue()
+		v := make(map[resource.PropertyKey]resource.PropertyValue, len(obj))
+		for k, e := range obj {
+			_, etfs, eps := getInfoFromPulumiName(k, tfflds, psflds, rawNames || useRawNames(tfs))
+			if isInput := isMap || etfs != nil && (etfs.Optional() || etfs.Required()); !isInput {
+				glog.V(9).Infof("skipping '%v' (not an input)", k)
+				continue
+			}
 
-		// Otherwise, copy it to the result.
-		copy, err := copystructure.Copy(value)
-		if err != nil {
-			return resource.PropertyValue{}, err
+			ev := extractSchemaInputs(e, etfs, eps, rawNames || useRawNames(tfs))
+			if mustSet := isMap || etfs != nil && (etfs.Required() || !isDefaultOrZeroValue(etfs, eps, ev)); !mustSet {
+				glog.V(9).Infof("skipping '%v' (not required + default or zero value)", k)
+				continue
+			}
+			v[k] = ev
 		}
-		inputs[name] = copy.(resource.PropertyValue)
+		v[defaultsKey] = resource.NewArrayProperty([]resource.PropertyValue{})
+		return resource.NewObjectProperty(v)
+	default:
+		return state
 	}
-
-	inputsValue := resource.NewObjectProperty(inputs)
-	addDefaultAnnotations(inputsValue)
-	return inputsValue, nil
 }
 
 func extractInputsFromOutputs(oldInputs, outs resource.PropertyMap,
@@ -1156,11 +1216,7 @@ func extractInputsFromOutputs(oldInputs, outs resource.PropertyMap,
 			resource.NewObjectProperty(outs), sch, pss, false)
 	} else {
 		// Otherwise, take a schema-directed approach that fills out all input-only properties.
-		v, err := extractSchemaInputs(resource.NewObjectProperty(outs), tfs, ps)
-		if err != nil {
-			return nil, err
-		}
-		inputs = v
+		inputs = extractSchemaInputs(resource.NewObjectProperty(outs), sch, pss, false)
 	}
 	return inputs.ObjectValue(), nil
 }
diff --git a/pkg/tfgen/docs.go b/pkg/tfgen/docs.go
index ee583337..805b1b43 100644
--- a/pkg/tfgen/docs.go
+++ b/pkg/tfgen/docs.go
@@ -175,9 +175,8 @@ func getRepoPath(gitHost string, org string, provider string, version string) (s
 	return target.Dir, nil
 }
 
-func getMarkdownDetails(g *Generator, org string, provider string, resourcePrefix string, kind DocKind,
-	rawname string, info tfbridge.ResourceOrDataSourceInfo, providerModuleVersion string,
-	githost string) ([]byte, string, bool) {
+func getMarkdownDetails(org string, provider string, resourcePrefix string, kind DocKind, rawname string,
+	info tfbridge.ResourceOrDataSourceInfo, providerModuleVersion string, githost string) ([]byte, string, bool) {
 
 	var docinfo *tfbridge.DocInfo
 	if info != nil {
@@ -227,8 +226,7 @@ func getDocsForProvider(g *Generator, org string, provider string, resourcePrefi
 		return entityDocs{}, nil
 	}
 
-	markdownBytes, markdownFileName, found := getMarkdownDetails(g, org, provider, resourcePrefix, kind, rawname, info,
-		providerModuleVersion, githost)
+	markdownBytes, markdownFileName, found := getMarkdownDetails(org, provider, resourcePrefix, kind, rawname, info, providerModuleVersion, githost)
 	if !found {
 		g.warn("Could not find docs for resource %v; consider overriding doc source location", rawname)
 		return entityDocs{}, nil
@@ -245,23 +243,17 @@ func getDocsForProvider(g *Generator, org string, provider string, resourcePrefi
 	}
 	if docinfo != nil {
 		// Merge Attributes from source into target
-		if err := mergeDocs(g, info, org, provider, resourcePrefix, kind, doc,
-			docinfo.IncludeAttributesFrom, true, true,
-			providerModuleVersion, githost); err != nil {
+		if err := mergeDocs(g, org, provider, resourcePrefix, kind, doc, docinfo.IncludeAttributesFrom, true, true, providerModuleVersion, githost); err != nil {
 			return doc, err
 		}
 
 		// Merge Arguments from source into Attributes of target
-		if err := mergeDocs(g, info, org, provider, resourcePrefix, kind, doc,
-			docinfo.IncludeAttributesFromArguments, true, false,
-			providerModuleVersion, githost); err != nil {
+		if err := mergeDocs(g, org, provider, resourcePrefix, kind, doc, docinfo.IncludeAttributesFromArguments, true, false, providerModuleVersion, githost); err != nil {
 			return doc, err
 		}
 
 		// Merge Arguments from source into target
-		if err := mergeDocs(g, info, org, provider, provider, kind, doc,
-			docinfo.IncludeArgumentsFrom, false, false,
-			providerModuleVersion, githost); err != nil {
+		if err := mergeDocs(g, org, provider, provider, kind, doc, docinfo.IncludeArgumentsFrom, false, false, providerModuleVersion, githost); err != nil {
 			return doc, err
 		}
 	}
@@ -308,10 +300,9 @@ func readMarkdown(repo string, kind DocKind, possibleLocations []string) ([]byte
 }
 
 // mergeDocs adds the docs specified by extractDoc from sourceFrom into the targetDocs
-func mergeDocs(g *Generator, info tfbridge.ResourceOrDataSourceInfo, org string, provider string,
-	resourcePrefix string,
-	kind DocKind, docs entityDocs, sourceFrom string,
-	useTargetAttributes bool, useSourceAttributes bool, providerModuleVersion string, githost string) error {
+func mergeDocs(g *Generator, org string, provider string, resourcePrefix string, kind DocKind, docs entityDocs,
+	sourceFrom string, useTargetAttributes bool, useSourceAttributes bool, providerModuleVersion string,
+	githost string) error {
 
 	if sourceFrom != "" {
 		sourceDocs, err := getDocsForProvider(g, org, provider, resourcePrefix, kind,
@@ -377,7 +368,7 @@ var (
 	attributionFormatString = "This Pulumi package is based on the [`%[1]s` Terraform Provider](https://%[3]s/%[2]s/terraform-provider-%[1]s)."
 )
 
-// groupLines groups a collection of strings, a, by a given separator, sep.
+// groupLines groups a collection of strings, lines, by a given separator, sep.
 func groupLines(lines []string, sep string) [][]string {
 	var buffer []string
 	var sections [][]string
@@ -453,7 +444,7 @@ func (p *tfMarkdownParser) parse() (entityDocs, error) {
 	sections := splitGroupLines(markdown, "## ")
 
 	// Reparent examples that are peers of the "Example Usage" section (if any) and fixup some example titles.
-	sections = p.reformatExamples(sections)
+	sections = reformatExamples(sections)
 
 	for _, section := range sections {
 		if err := p.parseSection(section); err != nil {
@@ -473,7 +464,7 @@ func (p *tfMarkdownParser) parse() (entityDocs, error) {
 }
 
 // fixExampleTitles transforms H4 sections that contain code snippets into H3 sections.
-func (p *tfMarkdownParser) fixExampleTitles(lines []string) {
+func fixExampleTitles(lines []string) {
 	inSection, sectionIndex := false, 0
 	for i, line := range lines {
 		if inSection && strings.HasPrefix(line, "```") {
@@ -489,7 +480,7 @@ var exampleHeaderRegexp = regexp.MustCompile(`(?i)^(## Example Usage\s*)(?:(?:(?
 
 // reformatExamples reparents examples that are peers of the "Example Usage" section (if any) and fixup some example
 // titles.
-func (p *tfMarkdownParser) reformatExamples(sections [][]string) [][]string {
+func reformatExamples(sections [][]string) [][]string {
 	canonicalExampleUsageSectionIndex := -1
 	var exampleUsageSection []string
 	var exampleSectionIndices []int
@@ -553,7 +544,7 @@ func (p *tfMarkdownParser) reformatExamples(sections [][]string) [][]string {
 	exampleUsageSection[0] = "## Example Usage"
 
 	// Fixup example titles and replace the contents of the canonical example usage section with the output.
-	p.fixExampleTitles(exampleUsageSection)
+	fixExampleTitles(exampleUsageSection)
 	sections[canonicalExampleUsageSectionIndex] = exampleUsageSection
 
 	// If there is only one example section, we're done. Otherwise, we need to remove all non-canonical example usage
@@ -593,7 +584,6 @@ func (p *tfMarkdownParser) parseSection(section []string) error {
 	switch header {
 	case "Timeout", "Timeouts", "User Project Override", "User Project Overrides":
 		p.g.debug("Ignoring doc section [%v] for [%v]", header, p.rawname)
-		ignoredDocSections++
 		ignoredDocHeaders[header]++
 		return nil
 	case "Example Usage":
@@ -616,7 +606,10 @@ func (p *tfMarkdownParser) parseSection(section []string) error {
 	var wroteHeader bool
 	for _, subsection := range groupLines(section[1:], "### ") {
 		if len(subsection) == 0 {
-			p.g.warn("Unparseable H3 doc section for %v; consider overriding doc source location", p.rawname)
+			// An unparseable H3 appears (as observed by building a few tier 1 providers) to typically be due to an
+			// empty section resulting from how we parse sections earlier in the docs generation process. Therefore, we
+			// log it as debug output:
+			p.g.debug("Unparseable H3 doc section for %v; consider overriding doc source location", p.rawname)
 			continue
 		}
 
@@ -868,16 +861,22 @@ func (p *tfMarkdownParser) parseFrontMatter(subsection []string) {
 }
 
 var (
-	ignoredDocSections     int
 	ignoredDocHeaders      = make(map[string]int)
-	hclBlocksSucceeded     int
-	hclBlocksFailed        int
 	hclFailures            = make(map[string]bool)
 	elidedDescriptions     int // i.e., we discard the entire description, including examples
 	elidedDescriptionsOnly int // we discarded the description proper, but were able to preserve the examples
 	elidedArguments        int
 	elidedNestedArguments  int
 	elidedAttributes       int
+
+	hclAllLangsConversionFailures int // examples that failed to convert in any language
+
+	// examples that failed to convert in one, but not all, languages. This is less severe impact because users will
+	// at least have code in another language to reference:
+	hclGoPartialConversionFailures         int
+	hclPythonPartialConversionFailures     int
+	hclTypeScriptPartialConversionFailures int
+	hclCSharpPartialConversionFailures     int
 )
 
 // isBlank returns true if the line is all whitespace.
@@ -888,14 +887,6 @@ func isBlank(line string) bool {
 // printDocStats outputs warnings and, if flags are set, stdout diagnostics pertaining to documentation conversion.
 func printDocStats(g *Generator, printIgnoreDetails, printHCLFailureDetails bool) {
 	// These summaries are printed on each run, to help us keep an eye on success/failure rates.
-	if ignoredDocSections > 0 {
-		g.warn("%d documentation sections ignored", ignoredDocSections)
-	}
-	if hclBlocksFailed > 0 {
-		g.warn("%d/%d documentation code blocks failed to convert",
-			hclBlocksFailed, hclBlocksFailed+hclBlocksSucceeded)
-	}
-
 	if elidedDescriptions > 0 {
 		g.warn("%d entity descriptions contained an <elided> reference and were dropped, including examples.", elidedDescriptions)
 	}
@@ -916,6 +907,26 @@ func printDocStats(g *Generator, printIgnoreDetails, printHCLFailureDetails bool
 		g.warn("%d attributes contained an <elided> reference and had their descriptions dropped.", elidedAttributes)
 	}
 
+	if hclAllLangsConversionFailures > 0 {
+		g.warn("%d HCL examples failed to convert in all languages", hclAllLangsConversionFailures)
+	}
+
+	if hclTypeScriptPartialConversionFailures > 0 {
+		g.warn("%d HCL examples were converted in at least one language but failed to convert to TypeScript", hclTypeScriptPartialConversionFailures)
+	}
+
+	if hclPythonPartialConversionFailures > 0 {
+		g.warn("%d HCL examples were converted in at least one language but failed to convert to Python", hclPythonPartialConversionFailures)
+	}
+
+	if hclGoPartialConversionFailures > 0 {
+		g.warn("%d HCL examples were converted in at least one language but failed to convert to Go", hclGoPartialConversionFailures)
+	}
+
+	if hclCSharpPartialConversionFailures > 0 {
+		g.warn("%d HCL examples were converted in at least one language but failed to convert to C#", hclCSharpPartialConversionFailures)
+	}
+
 	// These more detailed outputs are suppressed by default, but can be enabled to track down failures.
 	if printIgnoreDetails {
 		fmt.Printf("---IGNORES---\n")
@@ -971,7 +982,7 @@ func (p *tfMarkdownParser) reformatSubsection(lines []string) ([]string, bool, b
 	return result, hasExamples, isEmpty
 }
 
-// parseExamples converts any code snippets in a subsection to Pulumi-compatible code. This conversion is done on a
+// convertExamples converts any code snippets in a subsection to Pulumi-compatible code. This conversion is done on a
 // per-subsection basis; subsections with failing examples will be elided upon the caller's request.
 func (g *Generator) convertExamples(docs, name string, stripSubsectionsWithErrors bool) string {
 	if docs == "" {
@@ -1033,14 +1044,19 @@ func (g *Generator) convertExamples(docs, name string, stripSubsectionsWithError
 
 						// We've got some code -- assume it's HCL and try to convert it.
 						g.coverageTracker.foundExample(name, hcl)
-						codeBlock, stderr, err := g.convertHCL(hcl, name)
+
+						exampleTitle := ""
+						if strings.Contains(subsection[0], "###") {
+							exampleTitle = strings.Replace(subsection[0], "### ", "", -1)
+						}
+
+						codeBlock, stderr, err := g.convertHCL(hcl, name, exampleTitle)
+
 						if err != nil {
 							skippedExamples = true
 							hclFailures[stderr] = true
-							hclBlocksFailed++
 						} else {
 							fprintf(subsectionOutput, "\n%s", codeBlock)
-							hclBlocksSucceeded++
 						}
 					} else {
 						skippedExamples = true
@@ -1108,7 +1124,7 @@ func (g *Generator) convertExamples(docs, name string, stripSubsectionsWithError
 
 // convertHCL converts an in-memory, simple HCL program to Pulumi, and returns it as a string. In the event
 // of failure, the error returned will be non-nil, and the second string contains the stderr stream of details.
-func (g *Generator) convertHCL(hcl, path string) (string, string, error) {
+func (g *Generator) convertHCL(hcl, path, exampleTitle string) (string, string, error) {
 	g.debug("converting HCL for %s", path)
 
 	// Fixup the HCL as necessary.
@@ -1164,7 +1180,7 @@ func (g *Generator) convertHCL(hcl, path string) (string, string, error) {
 			}
 
 			g.coverageTracker.languageConversionPanic(languageName, err.Error())
-			g.error("failed to convert HCL for %s to %v: %v", path, languageName, err)
+			g.warn("failed to convert HCL for %s to %v: %v", path, languageName, err)
 			return fmt.Errorf("failed to convert HCL for %s to %v: %w", path, languageName, err)
 		}
 		if diags.All.HasErrors() {
@@ -1194,7 +1210,14 @@ func (g *Generator) convertHCL(hcl, path string) (string, string, error) {
 			if result.Len() > 0 {
 				result.WriteByte('\n')
 			}
-			_, err := fmt.Fprintf(&result, "```%s\n%s\n```", languageName, strings.TrimSpace(string(output)))
+			out := strings.TrimSpace(string(output))
+
+			if g.convertedCode == nil {
+				g.convertedCode = map[string][]byte{}
+			}
+			g.convertedCode[path] = []byte(out)
+
+			_, err := fmt.Fprintf(&result, "```%s\n%s\n```", languageName, out)
 			contract.IgnoreError(err)
 		}
 
@@ -1204,24 +1227,75 @@ func (g *Generator) convertHCL(hcl, path string) (string, string, error) {
 
 	switch g.language {
 	case NodeJS:
-		err = convertHCL("typescript")
+		err = convertHCL(convert.LanguageTypescript)
 	case Python:
-		err = convertHCL("python")
+		err = convertHCL(convert.LanguagePython)
 	case CSharp:
-		err = convertHCL("csharp")
+		err = convertHCL(convert.LanguageCSharp)
 	case Golang:
-		err = convertHCL("go")
+		err = convertHCL(convert.LanguageGo)
+	case PCL:
+		convertHCL(convert.LanguagePulumi)
+
 	case Schema:
 		langs := []string{"typescript", "python", "csharp", "go"}
-		var anySucceeded bool = false
+		failedLangs := map[string]error{}
+		var passedLangs []string
+
 		for _, lang := range langs {
+			resultLenBefore := result.Len()
+
+			// The inner function convertHCL above will return an error in the event of a panic, but in the event of an
+			// error the function will simply not append any content to result and swallow the error. Therefore, we
+			// need to account for both of these failure conditions. If neither of these conditions occurs, we can
+			// assume the HCL was converted successfully for this language.
 			if langErr := convertHCL(lang); langErr != nil {
+				failedLangs[lang] = langErr
 				err = multierror.Append(err, langErr)
+			} else if resultLenBefore == result.Len() {
+				failedLangs[lang] = langErr
 			} else {
-				anySucceeded = true
+				passedLangs = append(passedLangs, lang)
 			}
 		}
-		if anySucceeded {
+
+		if len(failedLangs) == len(langs) {
+			hclAllLangsConversionFailures++
+
+			if exampleTitle == "" {
+				g.warn(fmt.Sprintf("unable to convert HCL example for Pulumi entity '%s' for all languages. The example will be dropped from any generated docs or SDKs.", path))
+			} else {
+				g.warn(fmt.Sprintf("unable to convert HCL example '%s' for Pulumi entity '%s' for all languages. The example will be dropped from any generated docs or SDKs.", exampleTitle, path))
+			}
+		}
+
+		// Log the results when an example fails to convert to some languages, but not all
+		if len(failedLangs) > 0 && len(failedLangs) < len(langs) {
+			var failedLangsStrings []string
+
+			for lang, _ := range failedLangs {
+				failedLangsStrings = append(failedLangsStrings, lang)
+
+				switch lang {
+				case "typescript":
+					hclTypeScriptPartialConversionFailures++
+				case "python":
+					hclPythonPartialConversionFailures++
+				case "csharp":
+					hclCSharpPartialConversionFailures++
+				case "go":
+					hclGoPartialConversionFailures++
+				}
+			}
+
+			if exampleTitle == "" {
+				g.warn(fmt.Sprintf("unable to convert HCL example for Pulumi entity '%s' in the following language(s): %s. Examples for these languages will be dropped from any generated docs or SDKs.", path, strings.Join(failedLangsStrings, ", ")))
+			} else {
+				g.warn(fmt.Sprintf("unable to convert HCL example '%s' for Pulumi entity '%s' in the following language(s): %s. Examples for these languages will be dropped from any generated docs or SDKs.", exampleTitle, path, strings.Join(failedLangsStrings, ", ")))
+			}
+		}
+
+		if len(passedLangs) > 0 {
 			// At least one language out of the given set has been generated, which is considered a success
 			err = nil
 		}
diff --git a/pkg/tfgen/generate.go b/pkg/tfgen/generate.go
index 11fc118e..cc495c10 100644
--- a/pkg/tfgen/generate.go
+++ b/pkg/tfgen/generate.go
@@ -69,6 +69,8 @@ type Generator struct {
 	skipDocs         bool
 	skipExamples     bool
 	coverageTracker  *CoverageTracker
+
+	convertedCode map[string][]byte
 }
 
 type Language string
@@ -79,11 +81,12 @@ const (
 	Python Language = "python"
 	CSharp Language = "dotnet"
 	Schema Language = "schema"
+	PCL    Language = "pulumi"
 )
 
 func (l Language) shouldConvertExamples() bool {
 	switch l {
-	case Golang, NodeJS, Python, CSharp, Schema:
+	case Golang, NodeJS, Python, CSharp, Schema, PCL:
 		return true
 	}
 	return false
@@ -624,7 +627,7 @@ func NewGenerator(opts GeneratorOptions) (*Generator, error) {
 
 	// Ensure the language is valid.
 	switch lang {
-	case Golang, NodeJS, Python, CSharp, Schema:
+	case Golang, NodeJS, Python, CSharp, Schema, PCL:
 		// OK
 	default:
 		return nil, errors.Errorf("unrecognized language runtime: %s", lang)
@@ -748,7 +751,8 @@ func (g *Generator) Generate() error {
 	// Go ahead and let the language generator do its thing. If we're emitting the schema, just go ahead and serialize
 	// it out.
 	var files map[string][]byte
-	if g.language == Schema {
+	switch g.language {
+	case Schema:
 		// Omit the version so that the spec is stable if the version is e.g. derived from the current Git commit hash.
 		pulumiPackageSpec.Version = ""
 
@@ -757,7 +761,16 @@ func (g *Generator) Generate() error {
 			return errors.Wrapf(err, "failed to marshal schema")
 		}
 		files = map[string][]byte{"schema.json": bytes}
-	} else {
+	case PCL:
+		if g.skipExamples {
+			return fmt.Errorf("Cannot set skipExamples and get PCL")
+		}
+		files = map[string][]byte{}
+		for path, code := range g.convertedCode {
+			path = strings.TrimPrefix(path, "#/") + ".pp"
+			files[path] = code
+		}
+	default:
 		pulumiPackage, err := pschema.ImportSpec(pulumiPackageSpec, nil)
 		if err != nil {
 			return errors.Wrapf(err, "failed to import Pulumi schema")
@@ -1255,7 +1268,7 @@ func (g *Generator) gatherOverlays() (moduleMap, error) {
 		if csharpinfo := g.info.CSharp; csharpinfo != nil {
 			overlay = csharpinfo.Overlay
 		}
-	case Schema:
+	case Schema, PCL:
 		// N/A
 	default:
 		contract.Failf("unrecognized language: %s", g.language)


Test patch:
diff --git a/internal/testprovider/schema.go b/internal/testprovider/schema.go
index 06263e2a..95892eeb 100644
--- a/internal/testprovider/schema.go
+++ b/internal/testprovider/schema.go
@@ -65,6 +65,7 @@ func ProviderV1() *schemav1.Provider {
 						Elem: &schemav1.Resource{
 							Schema: map[string]*schemav1.Schema{
 								"kind":          {Type: schemav1.TypeString, Optional: true},
+								"opt_bool":      {Type: schemav1.TypeBool, Optional: true},
 								"configuration": {Type: schemav1.TypeMap, Required: true},
 							},
 						},
@@ -106,25 +107,30 @@ func ProviderV1() *schemav1.Provider {
 					return nil
 				},
 				Read: func(data *schemav1.ResourceData, p interface{}) error {
-					MustSetIfUnset(data, "bool_property_value", false)
-					MustSetIfUnset(data, "number_property_value", 42)
-					MustSetIfUnset(data, "float_property_value", 99.6767932)
-					MustSetIfUnset(data, "string_property_value", "ognirts")
-					MustSetIfUnset(data, "array_property_value", []interface{}{"an array"})
-					MustSetIfUnset(data, "object_property_value", map[string]interface{}{
-						"property_a": "a",
-						"property_b": "true",
-						"property.c": "some.value",
-					})
-					MustSetIfUnset(data, "nested_resources", []interface{}{
-						map[string]interface{}{
-							"configuration": map[string]interface{}{
-								"configurationValue": "true",
+					if data.Id() == "empty-resource-id" {
+						MustSetIfUnset(data, "array_property_value", []interface{}{"an array"})
+						MustSetIfUnset(data, "nested_resources", nil)
+					} else {
+						MustSetIfUnset(data, "bool_property_value", false)
+						MustSetIfUnset(data, "number_property_value", 42)
+						MustSetIfUnset(data, "float_property_value", 99.6767932)
+						MustSetIfUnset(data, "string_property_value", "ognirts")
+						MustSetIfUnset(data, "array_property_value", []interface{}{"an array"})
+						MustSetIfUnset(data, "object_property_value", map[string]interface{}{
+							"property_a": "a",
+							"property_b": "true",
+							"property.c": "some.value",
+						})
+						MustSetIfUnset(data, "nested_resources", []interface{}{
+							map[string]interface{}{
+								"configuration": map[string]interface{}{
+									"configurationValue": "true",
+								},
 							},
-						},
-					})
-					MustSetIfUnset(data, "set_property_value", []interface{}{"set member 1", "set member 2"})
-					MustSetIfUnset(data, "string_with_bad_interpolation", "some ${interpolated:value} with syntax errors")
+						})
+						MustSetIfUnset(data, "set_property_value", []interface{}{"set member 1", "set member 2"})
+						MustSetIfUnset(data, "string_with_bad_interpolation", "some ${interpolated:value} with syntax errors")
+					}
 					return nil
 				},
 				Update: func(data *schemav1.ResourceData, p interface{}) error {
@@ -380,6 +386,7 @@ func ProviderV2() *schemav2.Provider {
 						Elem: &schemav2.Resource{
 							Schema: map[string]*schemav2.Schema{
 								"kind":          {Type: schemav2.TypeString, Optional: true},
+								"opt_bool":      {Type: schemav2.TypeBool, Optional: true},
 								"configuration": {Type: schemav2.TypeMap, Required: true},
 							},
 						},
@@ -421,25 +428,30 @@ func ProviderV2() *schemav2.Provider {
 					return nil
 				},
 				Read: func(data *schemav2.ResourceData, p interface{}) error {
-					MustSetIfUnset(data, "bool_property_value", false)
-					MustSetIfUnset(data, "number_property_value", 42)
-					MustSetIfUnset(data, "float_property_value", 99.6767932)
-					MustSetIfUnset(data, "string_property_value", "ognirts")
-					MustSetIfUnset(data, "array_property_value", []interface{}{"an array"})
-					MustSetIfUnset(data, "object_property_value", map[string]interface{}{
-						"property_a": "a",
-						"property_b": "true",
-						"property.c": "some.value",
-					})
-					MustSetIfUnset(data, "nested_resources", []interface{}{
-						map[string]interface{}{
-							"configuration": map[string]interface{}{
-								"configurationValue": "true",
+					if data.Id() == "empty-resource-id" {
+						MustSetIfUnset(data, "array_property_value", []interface{}{"an array"})
+						MustSetIfUnset(data, "nested_resources", nil)
+					} else {
+						MustSetIfUnset(data, "bool_property_value", false)
+						MustSetIfUnset(data, "number_property_value", 42)
+						MustSetIfUnset(data, "float_property_value", 99.6767932)
+						MustSetIfUnset(data, "string_property_value", "ognirts")
+						MustSetIfUnset(data, "array_property_value", []interface{}{"an array"})
+						MustSetIfUnset(data, "object_property_value", map[string]interface{}{
+							"property_a": "a",
+							"property_b": "true",
+							"property.c": "some.value",
+						})
+						MustSetIfUnset(data, "nested_resources", []interface{}{
+							map[string]interface{}{
+								"configuration": map[string]interface{}{
+									"configurationValue": "true",
+								},
 							},
-						},
-					})
-					MustSetIfUnset(data, "set_property_value", []interface{}{"set member 1", "set member 2"})
-					MustSetIfUnset(data, "string_with_bad_interpolation", "some ${interpolated:value} with syntax errors")
+						})
+						MustSetIfUnset(data, "set_property_value", []interface{}{"set member 1", "set member 2"})
+						MustSetIfUnset(data, "string_with_bad_interpolation", "some ${interpolated:value} with syntax errors")
+					}
 					return nil
 				},
 				Update: func(data *schemav2.ResourceData, p interface{}) error {
diff --git a/pkg/tfbridge/provider_test.go b/pkg/tfbridge/provider_test.go
index 44182744..81c87cac 100644
--- a/pkg/tfbridge/provider_test.go
+++ b/pkg/tfbridge/provider_test.go
@@ -353,6 +353,7 @@ func testProviderPreview(t *testing.T, provider *Provider) {
 			"configuration": resource.NewObjectProperty(resource.PropertyMap{
 				"name": resource.NewStringProperty("foo"),
 			}),
+			"optBool": resource.NewBoolProperty(false),
 		}),
 	}.DeepEquals(outs))
 
@@ -415,6 +416,7 @@ func testProviderPreview(t *testing.T, provider *Provider) {
 		"configuration": resource.NewObjectProperty(resource.PropertyMap{
 			"name": resource.NewStringProperty("foo"),
 		}),
+		"optBool": resource.NewBoolProperty(false),
 	}).DeepEquals(outs["nestedResources"]))
 }
 
@@ -554,3 +556,103 @@ func TestProviderPreConfigureCallbackV2(t *testing.T) {
 	}
 	testProviderPreConfigureCallback(t, provider)
 }
+
+func testProviderRead(t *testing.T, provider *Provider, typeName tokens.Type) {
+	urn := resource.NewURN("stack", "project", "", typeName, "name")
+	readResp, err := provider.Read(context.Background(), &pulumirpc.ReadRequest{
+		Id:         string("resource-id"),
+		Urn:        string(urn),
+		Properties: nil,
+	})
+	assert.NoError(t, err)
+
+	assert.NotNil(t, readResp.GetInputs())
+	assert.NotNil(t, readResp.GetProperties())
+
+	ins, err := plugin.UnmarshalProperties(readResp.GetInputs(), plugin.MarshalOptions{KeepUnknowns: true})
+	assert.NoError(t, err)
+	// Check all the expected inputs were read
+	assert.NotContains(t, ins, "boolPropertyValue") // This was "false" from Read, but it's default is false
+	assert.Equal(t, resource.NewNumberProperty(42), ins["numberPropertyValue"])
+	assert.Equal(t, resource.NewNumberProperty(99.6767932), ins["floatPropertyValue"])
+	assert.Equal(t, resource.NewStringProperty("ognirts"), ins["stringPropertyValue"])
+	assert.Equal(t, resource.NewArrayProperty(
+		[]resource.PropertyValue{resource.NewStringProperty("an array")}), ins["arrayPropertyValues"])
+	assert.Equal(t, resource.NewObjectProperty(resource.PropertyMap{
+		"__defaults": resource.NewArrayProperty([]resource.PropertyValue{}),
+		"property_a": resource.NewStringProperty("a"),
+		"property_b": resource.NewStringProperty("true"),
+		"property.c": resource.NewStringProperty("some.value"),
+	}), ins["objectPropertyValue"])
+	assert.Equal(t, resource.NewObjectProperty(resource.PropertyMap{
+		"__defaults": resource.NewArrayProperty([]resource.PropertyValue{}),
+		"configuration": resource.NewObjectProperty(resource.PropertyMap{
+			"__defaults":         resource.NewArrayProperty([]resource.PropertyValue{}),
+			"configurationValue": resource.NewStringProperty("true"),
+		}),
+	}), ins["nestedResources"])
+	assert.Equal(t, resource.NewArrayProperty(
+		[]resource.PropertyValue{
+			resource.NewStringProperty("set member 2"),
+			resource.NewStringProperty("set member 1"),
+		}), ins["setPropertyValues"])
+	assert.Equal(t, resource.NewStringProperty("some ${interpolated:value} with syntax errors"), ins["stringWithBadInterpolation"])
+
+	// Read again with the ID that results in all the optinal fields not being set
+	readResp, err = provider.Read(context.Background(), &pulumirpc.ReadRequest{
+		Id:         string("empty-resource-id"),
+		Urn:        string(urn),
+		Properties: nil,
+	})
+	assert.NoError(t, err)
+
+	assert.NotNil(t, readResp.GetInputs())
+	assert.NotNil(t, readResp.GetProperties())
+
+	ins, err = plugin.UnmarshalProperties(readResp.GetInputs(), plugin.MarshalOptions{KeepUnknowns: true})
+	assert.NoError(t, err)
+	// Check all the expected inputs were read
+	assert.NotContains(t, ins, "boolPropertyValue")
+	assert.NotContains(t, ins, "numberPropertyValue")
+	assert.NotContains(t, ins, "floatPropertyValue")
+	assert.NotContains(t, ins, "stringPropertyValue")
+	assert.Equal(t, resource.NewArrayProperty(
+		[]resource.PropertyValue{resource.NewStringProperty("an array")}), ins["arrayPropertyValues"])
+	assert.NotContains(t, ins, "objectPropertyValue")
+	assert.NotContains(t, ins, "nestedResources")
+	assert.NotContains(t, ins, "setPropertyValues")
+	assert.NotContains(t, ins, "stringWithBadInterpolation")
+}
+
+func TestProviderReadV1(t *testing.T) {
+	provider := &Provider{
+		tf:     shimv1.NewProvider(testTFProvider),
+		config: shimv1.NewSchemaMap(testTFProvider.Schema),
+	}
+
+	provider.resources = map[tokens.Type]Resource{
+		"ExampleResource": {
+			TF:     shimv1.NewResource(testTFProvider.ResourcesMap["example_resource"]),
+			TFName: "example_resource",
+			Schema: &ResourceInfo{Tok: "ExampleResource"},
+		},
+	}
+
+	testProviderRead(t, provider, "ExampleResource")
+}
+
+func TestProviderReadV2(t *testing.T) {
+	provider := &Provider{
+		tf:     shimv2.NewProvider(testTFProviderV2),
+		config: shimv2.NewSchemaMap(testTFProviderV2.Schema),
+	}
+	provider.resources = map[tokens.Type]Resource{
+		"ExampleResource": {
+			TF:     shimv2.NewResource(testTFProviderV2.ResourcesMap["example_resource"]),
+			TFName: "example_resource",
+			Schema: &ResourceInfo{Tok: "ExampleResource"},
+		},
+	}
+
+	testProviderRead(t, provider, "ExampleResource")
+}
diff --git a/pkg/tfbridge/schema_test.go b/pkg/tfbridge/schema_test.go
index 396ee23d..a7aae612 100644
--- a/pkg/tfbridge/schema_test.go
+++ b/pkg/tfbridge/schema_test.go
@@ -1391,6 +1391,11 @@ func TestExtractInputsFromOutputs(t *testing.T) {
 					return strings.ToLower(v.(string))
 				},
 			},
+
+			// input_i, inout_j, and inout_k test import scenarios where attributes are set to "".
+			"input_i": {Type: schemav1.TypeString, Required: true},
+			"inout_j": {Type: schemav1.TypeString, Optional: true, Computed: true},
+			"inout_k": {Type: schemav1.TypeString, Optional: true, Computed: true, Default: "inout_k_default"},
 		},
 		func(d *schemav1.ResourceData, meta interface{}) ([]*schemav1.ResourceData, error) {
 			return []*schemav1.ResourceData{d}, nil
@@ -1413,6 +1418,9 @@ func TestExtractInputsFromOutputs(t *testing.T) {
 		}
 		set(d, "inout_d", "inout_d_read")
 		set(d, "output_g", "output_g_read")
+		set(d, "input_i", "")
+		set(d, "inout_j", "")
+		set(d, "inout_k", "")
 		return nil
 	}
 	tfres.Create = func(d *schemav1.ResourceData, meta interface{}) error {
@@ -1470,6 +1478,9 @@ func TestExtractInputsFromOutputs(t *testing.T) {
 		"inoutC":  "inout_c_read",
 		"inoutD":  "inout_d_read",
 		"outputG": "output_g_read",
+		"inputI":  "",
+		"inoutJ":  "",
+		"inoutK":  "",
 	}), outs)
 
 	ins, err := plugin.UnmarshalProperties(resp.GetInputs(), plugin.MarshalOptions{})
@@ -1479,6 +1490,8 @@ func TestExtractInputsFromOutputs(t *testing.T) {
 		"inputA":    "input_a_read",
 		"inoutC":    "inout_c_read",
 		"inoutD":    "inout_d_read",
+		"inputI":    "",
+		"inoutK":    "",
 	})
 	assert.Equal(t, expected, ins)
 
@@ -1505,7 +1518,7 @@ func TestExtractInputsFromOutputs(t *testing.T) {
 	assert.NoError(t, err)
 	sortDefaultsList(checkedIns)
 	assert.Equal(t, resource.NewPropertyMapFromMap(map[string]interface{}{
-		defaultsKey: []interface{}{"inoutD", "inputF", "inputH"},
+		defaultsKey: []interface{}{"inoutD", "inoutK", "inputF", "inputH"},
 		"inputA":    "input_a_create",
 		"inoutD":    "inout_d_default",
 		"inputE": map[string]interface{}{
@@ -1514,6 +1527,7 @@ func TestExtractInputsFromOutputs(t *testing.T) {
 		},
 		"inputF": "input_f_default",
 		"inputH": "Input_H_Default",
+		"inoutK": "inout_k_default",
 	}), checkedIns)
 
 	// Step 2: create a resource using the checked input bag. The inputs should be smuggled along with the state.
@@ -1536,6 +1550,7 @@ func TestExtractInputsFromOutputs(t *testing.T) {
 		"inputF":  "input_f_default",
 		"outputG": "output_g_create",
 		"inputH":  "input_h_default",
+		"inoutK":  "inout_k_default",
 	}), outs)
 
 	// Step 3: read the resource we just created. The read should make the following changes to the inputs:
@@ -1565,6 +1580,9 @@ func TestExtractInputsFromOutputs(t *testing.T) {
 		"inputF":  "input_f_default",
 		"outputG": "output_g_read",
 		"inputH":  "input_h_default",
+		"inputI":  "",
+		"inoutJ":  "",
+		"inoutK":  "",
 	}), outs)
 
 	ins, err = plugin.UnmarshalProperties(resp.GetInputs(), plugin.MarshalOptions{})
@@ -1579,6 +1597,7 @@ func TestExtractInputsFromOutputs(t *testing.T) {
 		},
 		"inputF": "input_f_default",
 		"inputH": "Input_H_Default",
+		"inoutK": "",
 	}), ins)
 
 	// Step 3a. delete the default annotations from the checked inputs and re-run the read. No default annotations
@@ -1609,6 +1628,9 @@ func TestExtractInputsFromOutputs(t *testing.T) {
 		"inputF":  "input_f_default",
 		"outputG": "output_g_read",
 		"inputH":  "input_h_default",
+		"inputI":  "",
+		"inoutJ":  "",
+		"inoutK":  "",
 	}), outs)
 
 	ins, err = plugin.UnmarshalProperties(resp.GetInputs(), plugin.MarshalOptions{})
@@ -1621,6 +1643,7 @@ func TestExtractInputsFromOutputs(t *testing.T) {
 		},
 		"inputF": "input_f_default",
 		"inputH": "Input_H_Default",
+		"inoutK": "",
 	}), ins)
 
 }
@@ -1934,3 +1957,140 @@ func TestDeleteBeforeReplaceAutoname(t *testing.T) {
 	assert.True(t, len(diffResp.GetReplaces()) > 0)
 	assert.False(t, diffResp.GetDeleteBeforeReplace())
 }
+
+func TestExtractDefaultSecretInputs(t *testing.T) {
+	tfProvider := makeTestTFProvider(
+		map[string]*schemav1.Schema{
+			"input_a": {Type: schemav1.TypeString, Sensitive: true, Required: true},
+			"input_b": {Type: schemav1.TypeString, Sensitive: true, Optional: true},
+			"input_c": {Type: schemav1.TypeString, Sensitive: true, Optional: true, Computed: true},
+			"input_d": {Type: schemav1.TypeString, Sensitive: true, Optional: true, Default: "input_d_default"},
+		},
+		func(d *schemav1.ResourceData, meta interface{}) ([]*schemav1.ResourceData, error) {
+			return []*schemav1.ResourceData{d}, nil
+		})
+
+	set := func(d *schemav1.ResourceData, key string, value interface{}) {
+		contract.IgnoreError(d.Set(key, value))
+	}
+
+	tfres := tfProvider.ResourcesMap["importable_resource"]
+	tfres.Read = func(d *schemav1.ResourceData, meta interface{}) error {
+		_, ok := d.GetOk(defaultsKey)
+		assert.False(t, ok)
+
+		set(d, "input_a", "input_a_read")
+		set(d, "input_c", "input_c_read")
+		set(d, "input_d", "input_d_default")
+		return nil
+	}
+
+	p := &Provider{
+		tf: shimv1.NewProvider(tfProvider),
+		resources: map[tokens.Type]Resource{
+			"importableResource": {
+				TF:     shimv1.NewResource(tfProvider.ResourcesMap["importable_resource"]),
+				TFName: "importable_resource",
+				Schema: &ResourceInfo{
+					Tok: tokens.NewTypeToken("module", "importableResource"),
+				},
+			},
+		},
+	}
+
+	urn := resource.NewURN("s", "pr", "pa", "importableResource", "n")
+	id := resource.ID("MyID")
+
+	resp, err := p.Read(context.Background(), &pulumirpc.ReadRequest{
+		Id:  string(id),
+		Urn: string(urn),
+	})
+	assert.NoError(t, err)
+
+	outs, err := plugin.UnmarshalProperties(resp.GetProperties(), plugin.MarshalOptions{})
+	assert.NoError(t, err)
+	assert.Equal(t, resource.NewPropertyMapFromMap(map[string]interface{}{
+		"id":     "MyID",
+		"inputA": "input_a_read",
+		"inputC": "input_c_read",
+		"inputD": "input_d_default",
+	}), outs)
+
+	ins, err := plugin.UnmarshalProperties(resp.GetInputs(), plugin.MarshalOptions{})
+	assert.NoError(t, err)
+	expected := resource.NewPropertyMapFromMap(map[string]interface{}{
+		defaultsKey: []interface{}{},
+		"inputA":    "input_a_read",
+		"inputC":    "input_c_read",
+	})
+	assert.Equal(t, expected, ins)
+}
+
+func TestExtractDefaultIntegerInputs(t *testing.T) {
+	// Terrafrom differentiates between Int and Float. Pulumi doesn't so we need to handle both cases for default values.
+	tfProvider := makeTestTFProvider(
+		map[string]*schemav1.Schema{
+			"input_a": {Type: schemav1.TypeInt, Optional: true},
+			"input_b": {Type: schemav1.TypeFloat, Optional: true},
+			"input_c": {Type: schemav1.TypeInt, Optional: true, Default: -1},
+			"input_d": {Type: schemav1.TypeFloat, Optional: true, Default: -1},
+		},
+		func(d *schemav1.ResourceData, meta interface{}) ([]*schemav1.ResourceData, error) {
+			return []*schemav1.ResourceData{d}, nil
+		})
+
+	set := func(d *schemav1.ResourceData, key string, value interface{}) {
+		contract.IgnoreError(d.Set(key, value))
+	}
+
+	tfres := tfProvider.ResourcesMap["importable_resource"]
+	tfres.Read = func(d *schemav1.ResourceData, meta interface{}) error {
+		_, ok := d.GetOk(defaultsKey)
+		assert.False(t, ok)
+
+		set(d, "input_a", 0)
+		set(d, "input_b", 0)
+		set(d, "input_c", -1)
+		set(d, "input_d", -1)
+		return nil
+	}
+
+	p := &Provider{
+		tf: shimv1.NewProvider(tfProvider),
+		resources: map[tokens.Type]Resource{
+			"importableResource": {
+				TF:     shimv1.NewResource(tfProvider.ResourcesMap["importable_resource"]),
+				TFName: "importable_resource",
+				Schema: &ResourceInfo{
+					Tok: tokens.NewTypeToken("module", "importableResource"),
+				},
+			},
+		},
+	}
+
+	urn := resource.NewURN("s", "pr", "pa", "importableResource", "n")
+	id := resource.ID("MyID")
+
+	resp, err := p.Read(context.Background(), &pulumirpc.ReadRequest{
+		Id:  string(id),
+		Urn: string(urn),
+	})
+	assert.NoError(t, err)
+
+	outs, err := plugin.UnmarshalProperties(resp.GetProperties(), plugin.MarshalOptions{})
+	assert.NoError(t, err)
+	assert.Equal(t, resource.NewPropertyMapFromMap(map[string]interface{}{
+		"id":     "MyID",
+		"inputA": 0,
+		"inputB": 0,
+		"inputC": -1,
+		"inputD": -1,
+	}), outs)
+
+	ins, err := plugin.UnmarshalProperties(resp.GetInputs(), plugin.MarshalOptions{})
+	assert.NoError(t, err)
+	expected := resource.NewPropertyMapFromMap(map[string]interface{}{
+		defaultsKey: []interface{}{},
+	})
+	assert.Equal(t, expected, ins)
+}
diff --git a/pkg/tfgen/docs_test.go b/pkg/tfgen/docs_test.go
index 7b158b3f..9ea4fee7 100644
--- a/pkg/tfgen/docs_test.go
+++ b/pkg/tfgen/docs_test.go
@@ -275,8 +275,6 @@ func TestReplaceFooterLinks(t *testing.T) {
 }
 
 func TestFixExamplesHeaders(t *testing.T) {
-	p := &tfMarkdownParser{}
-
 	codeFence := "```"
 	t.Run("WithCodeFences", func(t *testing.T) {
 		markdown := `
@@ -296,7 +294,7 @@ Provides a DigitalOcean CDN Endpoint resource for use with Spaces.
 		var processedMarkdown string
 		groups := splitGroupLines(markdown, "## ")
 		for _, lines := range groups {
-			p.fixExampleTitles(lines)
+			fixExampleTitles(lines)
 			for _, line := range lines {
 				processedMarkdown += line
 			}
@@ -323,7 +321,7 @@ Misleading example title without any actual code fences. We should not modify th
 		var processedMarkdown string
 		groups := splitGroupLines(markdown, "## ")
 		for _, lines := range groups {
-			p.fixExampleTitles(lines)
+			fixExampleTitles(lines)
 			for _, line := range lines {
 				processedMarkdown += line
 			}
@@ -356,3 +354,97 @@ Basic usage:`
 `
 	assert.Equal(t, "", extractExamples(multipleExampleUsages))
 }
+
+func TestReformatExamples(t *testing.T) {
+	runTest := func(input string, expected [][]string) {
+		inputSections := splitGroupLines(input, "## ")
+		output := reformatExamples(inputSections)
+
+		assert.ElementsMatch(t, expected, output)
+	}
+
+	// This is a simple use case. We expect no changes to the original doc:
+	simpleDoc := `description
+
+## Example Usage
+
+example usage content`
+
+	simpleDocExpected := [][]string{
+		{
+			"description",
+			"",
+		},
+		{
+			"## Example Usage",
+			"",
+			"example usage content",
+		},
+	}
+
+	runTest(simpleDoc, simpleDocExpected)
+
+	// This use case demonstrates 2 examples at the same H2 level: a canonical Example Usage and another example
+	// for a specific use case. We expect these to be transformed into a canonical H2 "Example Usage" with an H3 for
+	// the specific use case.
+	// This scenario is common in the pulumi-gcp provider:
+	gcpDoc := `description
+
+## Example Usage
+
+example usage content
+
+## Example Usage - Specific Case
+
+specific case content`
+
+	gcpDocExpected := [][]string{
+		{
+			"description",
+			"",
+		},
+		{
+			"## Example Usage",
+			"",
+			"example usage content",
+			"",
+			"### Specific Case",
+			"",
+			"specific case content",
+		},
+	}
+
+	runTest(gcpDoc, gcpDocExpected)
+
+	// This use case demonstrates 2 no canonical Example Usage/basic case and 2 specific use cases. We expect the
+	// function to add a canonical Example Usage section with the 2 use cases as H3's beneath the canonical section.
+	// This scenario is common in the pulumi-gcp provider:
+	gcpDoc2 := `description
+
+## Example Usage - 1
+
+content 1
+
+## Example Usage - 2
+
+content 2`
+
+	gcpDoc2Expected := [][]string{
+		{
+			"description",
+			"",
+		},
+		{
+			"## Example Usage",
+			"### 1",
+			"",
+			"content 1",
+			"",
+			"### 2",
+			"",
+			"content 2",
+		},
+	}
+
+	runTest(gcpDoc2, gcpDoc2Expected)
+}
diff --git a/pkg/tfshim/tfplugin5/provider_test.go b/pkg/tfshim/tfplugin5/provider_test.go
index 408ec293..3ad66dc2 100644
--- a/pkg/tfshim/tfplugin5/provider_test.go
+++ b/pkg/tfshim/tfplugin5/provider_test.go
@@ -231,6 +231,7 @@ func TestProviderResourcesMap(t *testing.T) {
 				"array_property_value":  cty.List(cty.String),
 				"object_property_value": cty.Map(cty.String),
 				"nested_resources": cty.List(cty.Object(map[string]cty.Type{
+					"opt_bool":      cty.Bool,
 					"kind":          cty.String,
 					"configuration": cty.Map(cty.String),
 				})),
@@ -311,16 +312,23 @@ func TestProviderResourcesMap(t *testing.T) {
 				},
 				"nested_resources": &attributeSchema{
 					ctyType: cty.List(cty.Object(map[string]cty.Type{
+						"opt_bool":      cty.Bool,
 						"kind":          cty.String,
 						"configuration": cty.Map(cty.String),
 					})),
 					valueType: shim.TypeList,
 					elem: &resource{
 						ctyType: cty.Object(map[string]cty.Type{
+							"opt_bool":      cty.Bool,
 							"kind":          cty.String,
 							"configuration": cty.Map(cty.String),
 						}),
 						schema: schema.SchemaMap{
+							"opt_bool": &attributeSchema{
+								ctyType:   cty.Bool,
+								valueType: shim.TypeBool,
+								optional:  true,
+							},
 							"kind": &attributeSchema{
 								ctyType:   cty.String,
 								valueType: shim.TypeString,
@@ -923,6 +931,7 @@ func TestDiff(t *testing.T) {
 				"array_property_value":  cty.ListValEmpty(cty.String),
 				"object_property_value": cty.NullVal(cty.Map(cty.String)),
 				"nested_resources": cty.ListValEmpty(cty.Object(map[string]cty.Type{
+					"opt_bool":      cty.Bool,
 					"kind":          cty.String,
 					"configuration": cty.Map(cty.String),
 				})),
@@ -1032,7 +1041,8 @@ func TestApply(t *testing.T) {
 				"array_property_value": []interface{}{"foo"},
 				"nested_resources": []interface{}{
 					map[string]interface{}{
-						"kind": cty.StringVal(""),
+						"opt_bool": cty.BoolVal(false),
+						"kind":     cty.StringVal(""),
 						"configuration": map[string]interface{}{
 							"baz": "qux",
 						},
@@ -1060,7 +1070,8 @@ func TestApply(t *testing.T) {
 					"property.c": cty.StringVal("some.value"),
 				}),
 				"nested_resources": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
-					"kind": cty.StringVal(""),
+					"opt_bool": cty.BoolVal(false),
+					"kind":     cty.StringVal(""),
 					"configuration": cty.MapVal(map[string]cty.Value{
 						"configurationValue": cty.StringVal("true"),
 					}),
@@ -1112,7 +1123,8 @@ func TestApply(t *testing.T) {
 						"property.c": cty.StringVal("some.value"),
 					}),
 					"nested_resources": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
-						"kind": cty.StringVal(""),
+						"opt_bool": cty.BoolVal(false),
+						"kind":     cty.StringVal(""),
 						"configuration": cty.MapVal(map[string]cty.Value{
 							"configurationValue": cty.StringVal("true"),
 						}),
@@ -1198,7 +1210,8 @@ func TestRefresh(t *testing.T) {
 			"property.c": cty.StringVal("some.value"),
 		}),
 		"nested_resources": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
-			"kind": cty.StringVal(""),
+			"opt_bool": cty.BoolVal(false),
+			"kind":     cty.StringVal(""),
 			"configuration": cty.MapVal(map[string]cty.Value{
 				"configurationValue": cty.StringVal("true"),
 			}),