Skip to content

Commit

Permalink
Add ability to constrain supported languages of resource and function…
Browse files Browse the repository at this point in the history
… overlays (#16579)

The existing overlays (e.g. Chart v3 in Kubernetes, or CallbackFunction
in AWS) are not available in every language Pulumi supports. This often
confuses users because the generated docs include all languages Pulumi
supports (e.g. see
pulumi/pulumi-kubernetes#2181).

To solve that problem, this change adds a new optional parameter to the
schema that allows configuring the languages an overlay (resource or
function) supports.
To support this in docsgen the existing Language Chooser
(`LangChooserLanguages`) of resources is made configurable and extended
to functions.

Note: This doesn't support resource methods right now. They'll need
extra handling because and overlay resource method might not support all
of the languages its resource supports. I'll tackle this in a follow up
PR.

Here's a screenshot of how this will look like for the Helm v3 chart for
example:
<img width="1046" alt="Screenshot 2024-07-01 at 16 11 23"
src="https://github.com/pulumi/pulumi/assets/2453580/b1a1365a-6dee-4099-829a-2859639a4c8c">

The PR contains the following commits. I'd recommend to look at the
first three ones and then check the regenerated golden files in the last
one:
- **Add schema parameter to constrain supported languages for overlays**
- **Update developer docs and changelog**
- **Refactor LanguageChooser and always pass supported languages**
- **Regenerate testdata**

relates to #13231
  • Loading branch information
flostadler authored and glena committed Jul 11, 2024
1 parent 4acce6d commit 5a6b583
Show file tree
Hide file tree
Showing 202 changed files with 4,506 additions and 374 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
changes:
- type: feat
scope: docs
description: Add ability to constrain supported languages of resource and function overlays
11 changes: 11 additions & 0 deletions developer-docs/providers/metaschema.md
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,17 @@ Indicates that the implementation of the resource should not be generated from t

---

#### `overlaySupportedLanguages`

Indicates what languages the overlay supports. This only has an effect if the Resource is an Overlay (IsOverlay == true).
Supported values are "nodejs", "python", "go", "csharp", "java", "yaml".

`array`

Items: `string`

---

#### `methods`

A map from method name to function token that describes the resource's method set.
Expand Down
13 changes: 7 additions & 6 deletions pkg/codegen/docs/description.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package docs

import (
"fmt"
"strings"

"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
Expand Down Expand Up @@ -55,7 +56,7 @@ func getCodeSection(doc string) []codeLocation {
return fences
}

func markupBlock(block string) string {
func markupBlock(block, supportedSnippetLanguages string) string {
languages := []struct{ tag, choosable string }{
{"typescript", "<div>\n<pulumi-choosable type=\"language\" values=\"javascript,typescript\">\n\n"},
{"python", "<div>\n<pulumi-choosable type=\"language\" values=\"python\">\n\n"},
Expand All @@ -66,13 +67,13 @@ func markupBlock(block string) string {
}
const (
//nolint:lll
chooserStart = "<div>\n<pulumi-chooser type=\"language\" options=\"typescript,python,go,csharp,java,yaml\"></pulumi-chooser>\n</div>\n"
choosableEnd = "</pulumi-choosable>\n</div>\n"
chooserStartFmt = "<div>\n<pulumi-chooser type=\"language\" options=\"%s\"></pulumi-chooser>\n</div>\n"
choosableEnd = "</pulumi-choosable>\n</div>\n"
)

var markedUpBlock strings.Builder
// first, append the start chooser
markedUpBlock.WriteString(chooserStart)
markedUpBlock.WriteString(fmt.Sprintf(chooserStartFmt, supportedSnippetLanguages))

for _, lang := range languages {
// Add language specific open choosable
Expand All @@ -97,7 +98,7 @@ func markupBlock(block string) string {
return markedUpBlock.String()
}

func (dctx *docGenContext) processDescription(description string) docInfo {
func (dctx *docGenContext) processDescription(description, supportedSnippetLanguages string) docInfo {
importDetails := ""
parts := strings.Split(description, "\n\n## Import")
if len(parts) > 1 {
Expand All @@ -114,7 +115,7 @@ func (dctx *docGenContext) processDescription(description string) docInfo {
markedUpDescription += description[startIndex:block.open]
codeBlock := description[block.open:block.close]
// append marked up block
markedUpDescription += markupBlock(codeBlock)
markedUpDescription += markupBlock(codeBlock, supportedSnippetLanguages)
startIndex = block.close + len(endCodeBlock)
}
// append remainder of description, if any
Expand Down
7 changes: 4 additions & 3 deletions pkg/codegen/docs/description_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ func TestProcessDescription(t *testing.T) {
t.Parallel()

input := readFile(t, filepath.Join("testdata", tt.prefix+".md"))

actual := newDocGenContext().processDescription(input).description
dctx := newDocGenContext()
actual := dctx.processDescription(input, dctx.getSupportedSnippetLanguages(false, nil)).description

autogold.ExpectFile(t, autogold.Raw(actual))
})
Expand All @@ -50,7 +50,8 @@ func TestDecomposeDocstringDescription(t *testing.T) {

input := readFile(t, filepath.Join("testdata", tt.prefix+".md"))

actual := newDocGenContext().decomposeDocstring(input).description
dctx := newDocGenContext()
actual := dctx.decomposeDocstring(input, dctx.getSupportedSnippetLanguages(false, nil)).description

autogold.ExpectFile(t, autogold.Raw(actual))
})
Expand Down
15 changes: 13 additions & 2 deletions pkg/codegen/docs/examples.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ import (

const defaultMissingExampleSnippetPlaceholder = "Coming soon!"

type examplesSection struct {
// Examples is a list of exampleSections. Each exampleSection contains a title and code snippets
Examples []exampleSection
// LangChooserLanguages is a comma-separated list of languages to pass to the
// language chooser shortcode. Use this to customize the languages shown for a
// resource. By default, the language chooser will show all languages supported
// by Pulumi for all resources.
// Supported values are "typescript", "python", "go", "csharp", "java", "yaml"
LangChooserLanguages string
}

type exampleSection struct {
Title string
// Snippets is a map of language to its code snippet, if any.
Expand All @@ -42,12 +53,12 @@ type docInfo struct {
importDetails string
}

func (dctx *docGenContext) decomposeDocstring(docstring string) docInfo {
func (dctx *docGenContext) decomposeDocstring(docstring, supportedSnippetLanguages string) docInfo {
if docstring == "" {
return docInfo{}
}
if strings.Contains(docstring, beginCodeBlock) {
return dctx.processDescription(docstring)
return dctx.processDescription(docstring, supportedSnippetLanguages)
}

languages := codegen.NewStringSet(dctx.snippetLanguages...)
Expand Down
54 changes: 48 additions & 6 deletions pkg/codegen/docs/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ type resourceDocArgs struct {
// language chooser shortcode. Use this to customize the languages shown for a
// resource. By default, the language chooser will show all languages supported
// by Pulumi for all resources.
// Supported values are "typescript", "python", "go", "csharp", "java", "yaml"
LangChooserLanguages string

// CreationExampleSyntax is a map from language to the rendered HTML for the
Expand All @@ -369,7 +370,7 @@ type resourceDocArgs struct {

// Comment represents the introductory resource comment.
Comment string
ExamplesSection []exampleSection
ExamplesSection examplesSection
DeprecationMessage string

// Import
Expand Down Expand Up @@ -486,6 +487,42 @@ func (mod *modContext) withDocGenContext(dctx *docGenContext) *modContext {
return &newctx
}

// getSupportedLanguages returns the list of supported languages based on the overlay configuration.
// If `isOverlay` is false or `overlaySupportedLanguages` is empty/nil, it returns the default list of supported languages.
// Otherwise, it filters the `overlaySupportedLanguages` to ensure that they are a subset of the default supported languages.
func (dctx *docGenContext) getSupportedLanguages(isOverlay bool, overlaySupportedLanguages []string) []string {
if !isOverlay || len(overlaySupportedLanguages) == 0 {
return dctx.supportedLanguages
}
var supportedLanguages []string
allLanguages := codegen.NewStringSet(dctx.supportedLanguages...)
for _, lang := range overlaySupportedLanguages {
if allLanguages.Has(lang) {
supportedLanguages = append(supportedLanguages, lang)
}
}

return supportedLanguages
}

// getSupportedSnippetLanguages returns a comma separated string containing the supported snippet languages for the given type
// based on the overlay configuration.
// If the type is not an overlay or if there are no overlay supported languages, all languages are supported.
// Internally this calls the getSupportedLanguages function to retrieve the supported languages and replaces "nodejs"
// with "typescript" because snippet languages expect "typescript" instead of "nodejs".
func (dctx *docGenContext) getSupportedSnippetLanguages(isOverlay bool, overlaySupportedLanguages []string) string {
supportedLanguages := dctx.getSupportedLanguages(isOverlay, overlaySupportedLanguages)
supportedSnippetLanguages := make([]string, len(supportedLanguages))
for idx, lang := range supportedLanguages {
if lang == "nodejs" {
lang = "typescript"
}
supportedSnippetLanguages[idx] = lang
}

return strings.Join(supportedSnippetLanguages, ",")
}

func resourceName(r *schema.Resource) string {
if r.IsProvider {
return "Provider"
Expand Down Expand Up @@ -1795,15 +1832,19 @@ func (mod *modContext) genResource(r *schema.Resource) resourceDocArgs {
maxNestedTypes = 200
}

docInfo := dctx.decomposeDocstring(r.Comment)
supportedSnippetLanguages := mod.docGenContext.getSupportedSnippetLanguages(r.IsOverlay, r.OverlaySupportedLanguages)
docInfo := dctx.decomposeDocstring(r.Comment, supportedSnippetLanguages)
data := resourceDocArgs{
Header: mod.genResourceHeader(r),

Tool: mod.tool,

Comment: docInfo.description,
DeprecationMessage: r.DeprecationMessage,
ExamplesSection: docInfo.examples,
Comment: docInfo.description,
DeprecationMessage: r.DeprecationMessage,
ExamplesSection: examplesSection{
Examples: docInfo.examples,
LangChooserLanguages: supportedSnippetLanguages,
},
ImportDocs: docInfo.importDetails,
CreationExampleSyntax: creationExampleSyntax,
ConstructorParams: renderedCtorParams,
Expand All @@ -1822,7 +1863,8 @@ func (mod *modContext) genResource(r *schema.Resource) resourceDocArgs {

Methods: mod.genMethods(r),

PackageDetails: packageDetails,
PackageDetails: packageDetails,
LangChooserLanguages: supportedSnippetLanguages,
}

return data
Expand Down
19 changes: 15 additions & 4 deletions pkg/codegen/docs/gen_function.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,16 @@ type functionDocArgs struct {
Header header

Tool string
// LangChooserLanguages is a comma-separated list of languages to pass to the
// language chooser shortcode. Use this to customize the languages shown for a
// function. By default, the language chooser will show all languages supported
// by Pulumi.
// Supported values are "typescript", "python", "go", "csharp", "java", "yaml"
LangChooserLanguages string

DeprecationMessage string
Comment string
ExamplesSection []exampleSection
ExamplesSection examplesSection

// FunctionName is a map of the language and the function name in that language.
FunctionName map[string]string
Expand Down Expand Up @@ -470,19 +476,24 @@ func (mod *modContext) genFunction(f *schema.Function) functionDocArgs {
Notes: def.Attribution,
}

docInfo := dctx.decomposeDocstring(f.Comment)
supportedSnippetLanguages := mod.docGenContext.getSupportedSnippetLanguages(f.IsOverlay, f.OverlaySupportedLanguages)
docInfo := dctx.decomposeDocstring(f.Comment, supportedSnippetLanguages)
args := functionDocArgs{
Header: mod.genFunctionHeader(f),

Tool: mod.tool,
Tool: mod.tool,
LangChooserLanguages: supportedSnippetLanguages,

FunctionName: funcNameMap,
FunctionArgs: mod.genFunctionArgs(f, funcNameMap, false /*outputVersion*/),
FunctionResult: mod.getFunctionResourceInfo(f, false /*outputVersion*/),

Comment: docInfo.description,
DeprecationMessage: f.DeprecationMessage,
ExamplesSection: docInfo.examples,
ExamplesSection: examplesSection{
Examples: docInfo.examples,
LangChooserLanguages: supportedSnippetLanguages,
},

InputProperties: inputProps,
OutputProperties: outputProps,
Expand Down
20 changes: 17 additions & 3 deletions pkg/codegen/docs/gen_method.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"sort"
"strings"

"github.com/golang/glog"
"github.com/pulumi/pulumi/pkg/v3/codegen/python"
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
"github.com/pulumi/pulumi/sdk/v3/go/common/slice"
Expand All @@ -37,7 +38,7 @@ type methodDocArgs struct {

DeprecationMessage string
Comment string
ExamplesSection []exampleSection
ExamplesSection examplesSection

// MethodName is a map of the language and the method name in that language.
MethodName map[string]string
Expand Down Expand Up @@ -94,7 +95,17 @@ func (mod *modContext) genMethod(r *schema.Resource, m *schema.Method) methodDoc
methodNameMap[lang] = docHelper.GetMethodName(m)
}

docInfo := dctx.decomposeDocstring(f.Comment)
defer glog.Flush()
resourceSnippetLanguages := mod.docGenContext.getSupportedSnippetLanguages(r.IsOverlay, r.OverlaySupportedLanguages)
methodSnippetLanguages := mod.docGenContext.getSupportedSnippetLanguages(f.IsOverlay, f.OverlaySupportedLanguages)
if resourceSnippetLanguages != methodSnippetLanguages {
// All code choosers are tied together. If the method doesn't support the same languages as the resource, we
// default to the resource's supported languages for consistency.
glog.Warningf("Resource %s (%s) and method %s (%s) have different supported snippet languages. Defaulting to the resources supported snippet languages.",
r.Token, resourceSnippetLanguages, m.Name, methodSnippetLanguages)
}

docInfo := dctx.decomposeDocstring(f.Comment, resourceSnippetLanguages)
args := methodDocArgs{
Title: title(m.Name, ""),

Expand All @@ -106,7 +117,10 @@ func (mod *modContext) genMethod(r *schema.Resource, m *schema.Method) methodDoc

Comment: docInfo.description,
DeprecationMessage: f.DeprecationMessage,
ExamplesSection: docInfo.examples,
ExamplesSection: examplesSection{
Examples: docInfo.examples,
LangChooserLanguages: resourceSnippetLanguages,
},

InputProperties: inputProps,
OutputProperties: outputProps,
Expand Down
Loading

0 comments on commit 5a6b583

Please sign in to comment.