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

feat: add terraform generator printer #3570

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions internal/core/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func Bootstrap(config *BootstrapConfig) (exitCode int, result interface{}, err e
flags := pflag.NewFlagSet(config.Args[0], pflag.ContinueOnError)
flags.StringVarP(&profileFlag, "profile", "p", "", "The config profile to use")
flags.StringVarP(&configPathFlag, "config", "c", "", "The path to the config file")
flags.StringVarP(&outputFlag, "output", "o", cliConfig.DefaultOutput, "Output format: json or human")
flags.StringVarP(&outputFlag, "output", "o", cliConfig.DefaultOutput, "Output format: json, yaml, terraform, human, wide or template")
flags.BoolVarP(&debug, "debug", "D", os.Getenv("SCW_DEBUG") == "true", "Enable debug mode")
// Ignore unknown flag
flags.ParseErrorsWhitelist.UnknownFlags = true
Expand Down Expand Up @@ -150,7 +150,7 @@ func Bootstrap(config *BootstrapConfig) (exitCode int, result interface{}, err e
isClientFromBootstrapConfig = false
client, err = createAnonymousClient(httpClient, config.BuildInfo)
if err != nil {
printErr := printer.Print(err, nil)
printErr := printer.Print(client, err, nil)
if printErr != nil {
_, _ = fmt.Fprintln(config.Stderr, printErr)
}
Expand Down Expand Up @@ -201,7 +201,7 @@ func Bootstrap(config *BootstrapConfig) (exitCode int, result interface{}, err e
// Load CLI config
cliCfg, err := cliConfig.LoadConfig(ExtractCliConfigPath(ctx))
if err != nil {
printErr := printer.Print(err, nil)
printErr := printer.Print(meta.Client, err, nil)
if printErr != nil {
_, _ = fmt.Fprintln(config.Stderr, printErr)
}
Expand Down Expand Up @@ -270,15 +270,15 @@ func Bootstrap(config *BootstrapConfig) (exitCode int, result interface{}, err e
if cliErr, ok := err.(*CliError); ok && cliErr.Code != 0 {
errorCode = cliErr.Code
}
printErr := printer.Print(err, nil)
printErr := printer.Print(meta.Client, err, nil)
if printErr != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
}
return errorCode, nil, err
}

if meta.command != nil {
printErr := printer.Print(meta.result, meta.command.getHumanMarshalerOpt())
printErr := printer.Print(meta.Client, meta.result, meta.command.getHumanMarshalerOpt())
if printErr != nil {
_, _ = fmt.Fprintln(config.Stderr, printErr)
}
Expand Down
73 changes: 72 additions & 1 deletion internal/core/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (

"github.com/scaleway/scaleway-cli/v2/internal/gofields"
"github.com/scaleway/scaleway-cli/v2/internal/human"
"github.com/scaleway/scaleway-cli/v2/internal/terraform"
"github.com/scaleway/scaleway-sdk-go/scw"
)

// Type defines an formatter format.
Expand All @@ -28,6 +30,9 @@ const (
// PrinterTypeYAML defines a YAML formatter.
PrinterTypeYAML = PrinterType("yaml")

// PrinterTypeYAML defines a Terraform formatter.
PrinterTypeTerraform = PrinterType("terraform")

// PrinterTypeHuman defines a human readable formatted formatter.
PrinterTypeHuman = PrinterType("human")

Expand All @@ -39,6 +44,13 @@ const (

// Option to enable pretty output on json printer.
PrinterOptJSONPretty = "pretty"

// Option to disable parents output on terraform printer.
PrinterOptTerraformSkipParents = "skip-parents"
// Option to disable children output on terraform printer.
PrinterOptTerraformSkipChildren = "skip-children"
// Option to disable parents and children output on terraform printer.
PrinterOptTerraformSkipParentsAndChildren = "skip-parents-and-children"
)

type PrinterConfig struct {
Expand Down Expand Up @@ -75,6 +87,11 @@ func NewPrinter(config *PrinterConfig) (*Printer, error) {
}
case PrinterTypeYAML.String():
printer.printerType = PrinterTypeYAML
case PrinterTypeTerraform.String():
err := setupTerraformPrinter(printer, printerOpt)
if err != nil {
return nil, err
}
case PrinterTypeTemplate.String():
err := setupTemplatePrinter(printer, printerOpt)
if err != nil {
Expand All @@ -100,6 +117,33 @@ func setupJSONPrinter(printer *Printer, opts string) error {
return nil
}

func setupTerraformPrinter(printer *Printer, opts string) error {
printer.printerType = PrinterTypeTerraform
switch opts {
case PrinterOptTerraformSkipParents:
printer.terraformSkipParents = true
case PrinterOptTerraformSkipChildren:
printer.terraformSkipChildren = true
case PrinterOptTerraformSkipParentsAndChildren:
printer.terraformSkipParents = true
printer.terraformSkipChildren = true
case "":
default:
return fmt.Errorf("invalid option %s for terraform outout. Valid options are: %s and %s", opts, PrinterOptTerraformSkipParents, PrinterOptTerraformSkipChildren)
}

terraformVersion, err := terraform.GetLocalClientVersion()
if err != nil {
return err
}

if terraformVersion.Major < 1 || (terraformVersion.Major == 1 && terraformVersion.Minor < 5) {
return fmt.Errorf("terraform version %s is not supported. Please upgrade to terraform >= 1.5.0", terraformVersion.String())
}

return nil
}

func setupTemplatePrinter(printer *Printer, opts string) error {
printer.printerType = PrinterTypeTemplate
if opts == "" {
Expand Down Expand Up @@ -139,14 +183,19 @@ type Printer struct {
// Enable pretty print on json output
jsonPretty bool

// Disable children fetching on terraform output
terraformSkipParents bool
// Disable children fetching on terraform output
terraformSkipChildren bool

// go template to use on template output
template *template.Template

// Allow to select specifics column in a table with human printer
humanFields []string
}

func (p *Printer) Print(data interface{}, opt *human.MarshalOpt) error {
func (p *Printer) Print(client *scw.Client, data interface{}, opt *human.MarshalOpt) error {
// No matter the printer type if data is a RawResult we should print it as is.
if rawResult, isRawResult := data.(RawResult); isRawResult {
_, err := p.stdout.Write(rawResult)
Expand All @@ -163,6 +212,8 @@ func (p *Printer) Print(data interface{}, opt *human.MarshalOpt) error {
err = p.printJSON(data)
case PrinterTypeYAML:
err = p.printYAML(data)
case PrinterTypeTerraform:
err = p.printTerraform(client, data)
case PrinterTypeTemplate:
err = p.printTemplate(data)
default:
Expand Down Expand Up @@ -283,6 +334,26 @@ func (p *Printer) printYAML(data interface{}) error {
return encoder.Encode(data)
}

func (p *Printer) printTerraform(client *scw.Client, data interface{}) error {
writer := p.stdout
if _, isError := data.(error); isError {
return p.printHuman(data, nil)
}

hcl, err := terraform.GetHCL(&terraform.GetHCLConfig{
Client: client,
Data: data,
SkipParents: p.terraformSkipParents,
SkipChildren: p.terraformSkipChildren,
})
if err != nil {
return err
}

_, err = writer.Write([]byte(hcl))
return err
}

func (p *Printer) printTemplate(data interface{}) error {
writer := p.stdout
if _, isError := data.(error); isError {
Expand Down
4 changes: 2 additions & 2 deletions internal/core/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ func shellExecutor(rootCmd *cobra.Command, printer *Printer, meta *meta) func(s
return
}

printErr := printer.Print(err, nil)
printErr := printer.Print(meta.Client, err, nil)
if printErr != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
}
Expand All @@ -283,7 +283,7 @@ func shellExecutor(rootCmd *cobra.Command, printer *Printer, meta *meta) func(s

autoCompleteCache.Update(meta.command.Namespace)

printErr := printer.Print(meta.result, meta.command.getHumanMarshalerOpt())
printErr := printer.Print(meta.Client, meta.result, meta.command.getHumanMarshalerOpt())
if printErr != nil {
_, _ = fmt.Fprintln(os.Stderr, printErr)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/core/shell_disabled.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

func RunShell(ctx context.Context, printer *Printer, meta *meta, rootCmd *cobra.Command, args []string) {
err := printer.Print(fmt.Errorf("shell is currently disabled on %s/%s", runtime.GOARCH, runtime.GOOS), nil)
err := printer.Print(meta.Client, fmt.Errorf("shell is currently disabled on %s/%s", runtime.GOARCH, runtime.GOOS), nil)
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
}
Expand Down
4 changes: 2 additions & 2 deletions internal/core/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -726,11 +726,11 @@ func marshalGolden(t *testing.T, ctx *CheckFuncCtx) string {
require.NoError(t, err)

if ctx.Err != nil {
err = jsonPrinter.Print(ctx.Err, nil)
err = jsonPrinter.Print(nil, ctx.Err, nil)
require.NoError(t, err)
}
if ctx.Result != nil {
err = jsonPrinter.Print(ctx.Result, nil)
err = jsonPrinter.Print(nil, ctx.Result, nil)
require.NoError(t, err)
}

Expand Down
138 changes: 138 additions & 0 deletions internal/terraform/association.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package terraform

import (
"reflect"

"github.com/scaleway/scaleway-sdk-go/api/account/v2"
"github.com/scaleway/scaleway-sdk-go/api/baremetal/v1"
container "github.com/scaleway/scaleway-sdk-go/api/container/v1beta1"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)

type associationParent struct {
Fetcher func(client *scw.Client, data interface{}) (interface{}, error)
AsDataSource bool
}

type associationChild struct {
// {
// [<child attribute>]: <parent attribute>
// }
ParentFieldMap map[string]string

Fetcher func(client *scw.Client, data interface{}) (interface{}, error)
}

type association struct {
ResourceName string
ImportFormat string
Parents map[string]*associationParent
Children []*associationChild
}

// const importFormatID = "{{ .Region }}/{{ .ID }}"
const importFormatZoneID = "{{ .Zone }}/{{ .ID }}"
const importFormatRegionID = "{{ .Region }}/{{ .ID }}"

var associations = map[interface{}]*association{
&baremetal.Server{}: {
ResourceName: "scaleway_baremetal_server",
ImportFormat: importFormatZoneID,
},
&instance.Server{}: {
ResourceName: "scaleway_instance_server",
ImportFormat: importFormatZoneID,
},
&container.Container{}: {
ResourceName: "scaleway_container",
ImportFormat: importFormatRegionID,
Parents: map[string]*associationParent{
"namespace_id": {
Fetcher: func(client *scw.Client, raw interface{}) (interface{}, error) {
api := container.NewAPI(client)
data := raw.(*container.Container)

return api.GetNamespace(&container.GetNamespaceRequest{
NamespaceID: data.NamespaceID,
Region: data.Region,
})
},
},
},
},
&container.Namespace{}: {
ResourceName: "scaleway_container_namespace",
ImportFormat: importFormatRegionID,
Parents: map[string]*associationParent{
"project_id": {
AsDataSource: true,
Fetcher: func(client *scw.Client, raw interface{}) (interface{}, error) {
api := account.NewAPI(client)
data := raw.(*container.Namespace)

return api.GetProject(&account.GetProjectRequest{

Check failure on line 74 in internal/terraform/association.go

View workflow job for this annotation

GitHub Actions / lint

SA1019: api.GetProject is deprecated: GetProject: Deprecated in favor of Account API v3. Retrieve information about an existing Project, specified by its Project ID. Its full details, including ID, name and description, are returned in the response object. (staticcheck)
ProjectID: data.ProjectID,
})
},
},
},
Children: []*associationChild{
{
ParentFieldMap: map[string]string{
"namespace_id": "id",
},
Fetcher: func(client *scw.Client, raw interface{}) (interface{}, error) {
api := container.NewAPI(client)
data := raw.(*container.Namespace)

res, err := api.ListContainers(&container.ListContainersRequest{
NamespaceID: data.ID,
Region: data.Region,
})
if err != nil {
return nil, err
}

return res.Containers, nil
},
},
},
},
&account.Project{}: {
ResourceName: "scaleway_account_project",
ImportFormat: "{{ .ID }}",
Children: []*associationChild{
{
ParentFieldMap: map[string]string{
"project_id": "id",
},
Fetcher: func(client *scw.Client, raw interface{}) (interface{}, error) {
api := container.NewAPI(client)
data := raw.(*account.Project)

res, err := api.ListNamespaces(&container.ListNamespacesRequest{
ProjectID: &data.ID,
})
if err != nil {
return nil, err
}

return res.Namespaces, nil
},
},
},
},
}

func getAssociation(data interface{}) (*association, bool) {
dataType := reflect.TypeOf(data)

for i, association := range associations {
if dataType == reflect.TypeOf(i) {
return association, true
}
}

return nil, false
}
Loading
Loading