Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write _index.md to local docs dir on Any TF Provider #2717

Merged
merged 11 commits into from
Dec 18, 2024
28 changes: 27 additions & 1 deletion dynamic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func initialSetup() (info.Provider, pfbridge.ProviderMetadata, func() error) {

var metadata pfbridge.ProviderMetadata
var fullDocs bool
var indexDocOutDir string
metadata = pfbridge.ProviderMetadata{
XGetSchema: func(ctx context.Context, req plugin.GetSchemaRequest) ([]byte, error) {
// Create a custom generator for schema. Examples will only be generated if `fullDocs` is set.
Expand Down Expand Up @@ -95,6 +96,29 @@ func initialSetup() (info.Provider, pfbridge.ProviderMetadata, func() error) {
info.SchemaPostProcessor(&packageSchema.PackageSpec)
}

if indexDocOutDir != "" {
// Create a custom generator for registry docs (_index.md).
indexGenerator, err := tfgen.NewGenerator(tfgen.GeneratorOptions{
Package: info.Name,
Version: info.Version,
Language: tfgen.RegistryDocs,
ProviderInfo: info,
Root: afero.NewBasePathFs(afero.NewOsFs(), indexDocOutDir),
Sink: diag.DefaultSink(os.Stdout, os.Stderr, diag.FormatOptions{
Color: colors.Always,
}),
XInMemoryDocs: false,
SkipExamples: false,
})
if err != nil {
return nil, errors.Wrapf(err, "failed to create generator")
}
_, err = indexGenerator.Generate()
if err != nil {
return nil, err
}
}

return json.Marshal(packageSchema.PackageSpec)
},
XParamaterize: func(ctx context.Context, req plugin.ParameterizeRequest) (plugin.ParameterizeResponse, error) {
Expand Down Expand Up @@ -165,13 +189,15 @@ func initialSetup() (info.Provider, pfbridge.ProviderMetadata, func() error) {
switch args.Remote {
case nil:
// We're using local args.
indexDocOutDir = args.Local.IndexDocOutDir
if args.Local.UpstreamRepoPath != "" {
info.UpstreamRepoPath = args.Local.UpstreamRepoPath
fullDocs = true
}
default:
indexDocOutDir = args.Remote.IndexDocOutDir
fullDocs = args.Remote.Docs
if fullDocs {
if fullDocs || indexDocOutDir != "" {
// Write the upstream files at this version to a temporary directory
tmpDir, err := os.MkdirTemp("", "upstreamRepoDir")
if err != nil {
Expand Down
30 changes: 22 additions & 8 deletions dynamic/parameterize/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,47 +40,54 @@ type RemoteArgs struct {
Name string
// Version is the (possibly empty) version constraint on the provider.
Version string

// Docs indicates if full schema documentation should be generated.
Docs bool
// IndexDocOutDir allows us to set a specific directory to write `_index.md` to.
IndexDocOutDir string
}

// LocalArgs represents a local TF provider referenced by path.
type LocalArgs struct {
// Path is the path to the provider binary. It can be relative or absolute.
Path string

// UpstreamRepoPath (if provided) is the local path to the dynamically bridged Terraform provider's repo.
//
// If set, full documentation will be generated for the provider.
// If not set, only documentation from the TF provider's schema will be used.
UpstreamRepoPath string
// IndexDocOutDir allows us to set a specific directory to write `_index.md` to.
IndexDocOutDir string
}

func ParseArgs(ctx context.Context, a []string) (Args, error) {
var args Args
var fullDocs bool
var upstreamRepoPath string
var indexDocOutDir string
cmd := cobra.Command{
Use: "./local | remote version",
RunE: func(cmd *cobra.Command, a []string) error {
var err error
args, err = parseArgs(cmd.Context(), a, fullDocs, upstreamRepoPath)
args, err = parseArgs(cmd.Context(), a, fullDocs, upstreamRepoPath, indexDocOutDir)
return err
},
Args: cobra.RangeArgs(1, 2),
}

cmd.Flags().BoolVar(&fullDocs, "fullDocs", false,
"Generate a schema with full docs, at the expense of speed")
cmd.Flags().StringVar(&upstreamRepoPath, "upstreamRepoPath", "",
"Specify a local file path to the root of the Git repository of the provider being dynamically bridged")
cmd.Flags().StringVar(&indexDocOutDir, "indexDocOutDir", "",
"Specify a local output directory for the provider's _index.md file")

// We hide docs flags since they are not intended for end users, and they may not be stable.
if !env.Dev.Value() {
contract.AssertNoErrorf(
errors.Join(
cmd.Flags().MarkHidden("fullDocs"),
cmd.Flags().MarkHidden("upstreamRepoPath"),
cmd.Flags().MarkHidden("indexDocOutDir"),
),
"impossible - these are static values and should never fail",
)
Expand All @@ -106,7 +113,7 @@ func ParseArgs(ctx context.Context, a []string) (Args, error) {
return args, cmd.ExecuteContext(ctx)
}

func parseArgs(_ context.Context, args []string, fullDocs bool, upstreamRepoPath string) (Args, error) {
func parseArgs(_ context.Context, args []string, fullDocs bool, upstreamRepoPath, indexDocOutDir string) (Args, error) {
// If we see a local prefix (starts with '.' or '/'), parse args for a local provider
if strings.HasPrefix(args[0], ".") || strings.HasPrefix(args[0], "/") {
if len(args) > 1 {
Expand All @@ -119,7 +126,13 @@ func parseArgs(_ context.Context, args []string, fullDocs bool, upstreamRepoPath
}
return Args{}, errors.New(msg)
}
return Args{Local: &LocalArgs{Path: args[0], UpstreamRepoPath: upstreamRepoPath}}, nil
return Args{
Local: &LocalArgs{
Path: args[0],
UpstreamRepoPath: upstreamRepoPath,
IndexDocOutDir: indexDocOutDir,
},
}, nil
}

if upstreamRepoPath != "" {
Expand All @@ -136,8 +149,9 @@ func parseArgs(_ context.Context, args []string, fullDocs bool, upstreamRepoPath
}

return Args{Remote: &RemoteArgs{
Name: args[0],
Version: version,
Docs: fullDocs,
Name: args[0],
Version: version,
Docs: fullDocs,
IndexDocOutDir: indexDocOutDir,
}}, nil
}
39 changes: 24 additions & 15 deletions dynamic/parameterize/args_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func TestParseArgs(t *testing.T) {
errMsg: autogold.Expect(`accepts between 1 and 2 arg(s), received 3`),
},
{
name: "empty third arg",
name: "empty fullDocs flag defaults false",
args: []string{"arg1", "arg2"},
expect: Args{Remote: &RemoteArgs{
Name: "arg1",
Expand All @@ -91,29 +91,38 @@ func TestParseArgs(t *testing.T) {
}},
},
{
name: "valid third arg true",
args: []string{"my-registry.io/typ", "1.2.3", "--fullDocs=true"},
name: "fullDocs flag invalid input",
args: []string{"my-registry.io/typ", "1.2.3", "--fullDocs=invalid-input"},
//nolint:lll
errMsg: autogold.Expect(
`invalid argument "invalid-input" for "--fullDocs" flag: strconv.ParseBool: parsing "invalid-input": invalid syntax`,
),
},
{
name: "indexDocOutDir flag empty",
args: []string{"arg1", "arg2", "--indexDocOutDir="},
expect: Args{Remote: &RemoteArgs{
Name: "my-registry.io/typ",
Version: "1.2.3",
Docs: true,
Name: "arg1",
Version: "arg2",
Docs: false,
IndexDocOutDir: "",
}},
},
{
name: "valid third arg false",
args: []string{"my-registry.io/typ", "1.2.3", "--fullDocs=false"},
name: "indexDocOutDir sets location",
args: []string{"arg1", "arg2", "--indexDocOutDir=localDir"},
expect: Args{Remote: &RemoteArgs{
Name: "my-registry.io/typ",
Version: "1.2.3",
Docs: false,
Name: "arg1",
Version: "arg2",
Docs: false,
IndexDocOutDir: "localDir",
}},
},
{
name: "third arg invalid input",
args: []string{"my-registry.io/typ", "1.2.3", "--fullDocs=invalid-input"},
//nolint:lll
name: "invalid flag",
args: []string{"arg1", "arg2", "--invalid=wrong"},
errMsg: autogold.Expect(
`invalid argument "invalid-input" for "--fullDocs" flag: strconv.ParseBool: parsing "invalid-input": invalid syntax`,
"unknown flag: --invalid",
),
},
}
Expand Down
61 changes: 61 additions & 0 deletions dynamic/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,67 @@ func TestSchemaGenerationFullDocs(t *testing.T) { //nolint:paralleltest
})
}

func TestSchemaGenerationIndexDocOutDir(t *testing.T) { //nolint:paralleltest
skipWindows(t)
type testCase struct {
name string
providerName string
version string
fullDocs string
indexDocOutDir string
}

testCases := []testCase{
{
name: "with full docs",
providerName: "hashicorp/random",
version: "3.6.3",
fullDocs: "true",
indexDocOutDir: "localdir",
},
{
name: "index file only",
providerName: "hashicorp/random",
version: "3.6.3",
fullDocs: "false",
indexDocOutDir: "indexfileonly",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
helper.Integration(t)
ctx := context.Background()

server := grpcTestServer(ctx, t)

result, err := server.Parameterize(ctx, &pulumirpc.ParameterizeRequest{
Parameters: &pulumirpc.ParameterizeRequest_Args{
Args: &pulumirpc.ParameterizeRequest_ParametersArgs{
Args: []string{tc.providerName, tc.version, "--fullDocs=" + tc.fullDocs, "--indexDocOutDir=" + tc.indexDocOutDir},
},
},
})
require.NoError(t, err)

assert.Equal(t, tc.version, result.Version)

_, err = server.GetSchema(ctx, &pulumirpc.GetSchemaRequest{
SubpackageName: result.Name,
SubpackageVersion: result.Version,
})

require.NoError(t, err)

indexFileBytes, err := os.ReadFile(tc.indexDocOutDir + "/_index.md")

require.NoError(t, err)
autogold.ExpectFile(t, autogold.Raw(indexFileBytes))
// Clean up generated file
os.Remove(tc.indexDocOutDir + "/_index.md")
})
}
}

func TestRandomCreate(t *testing.T) {
t.Parallel()
ctx := context.Background()
Expand Down
Loading
Loading