// Package cmd implements the CLI command tree. package cmd import ( "fmt" "os" "strings" "github.com/spf13/cobra" ) // --------------------------------------------------------------------------- // Template command group (PLATFORM-MANAGEMENT-API.md ยง5(b)): // molecule template {list, import --name --file =..., refresh} // All go to the tenant host with the Org API Key (AdminAuth). // --------------------------------------------------------------------------- var templateCmd = &cobra.Command{ Use: "template", Short: "Manage workspace templates", } func init() { templateCmd.AddCommand(templateListCmd, templateImportCmd, templateRefreshCmd) } var templateListCmd = &cobra.Command{ Use: "list", Short: "List workspace templates", RunE: runTemplateList, } func runTemplateList(_ *cobra.Command, _ []string) error { raw, err := newClient().ListTemplates() if err != nil { return fmt.Errorf("template list: %w", err) } return printRaw(raw) } var templateImportFlags struct { name string files []string // KEY=PATH pairs } var templateImportCmd = &cobra.Command{ Use: "import --name --file = [--file ...]", Short: "Import a template from local files", Long: `Imports a template (POST /templates/import). Each --file maps a path inside the template to a local file whose contents are read and uploaded. molecule template import --name my-tmpl \ --file org.yaml=./org.yaml \ --file config.yaml=./config.yaml`, RunE: runTemplateImport, } func init() { f := templateImportCmd.Flags() f.StringVar(&templateImportFlags.name, "name", "", "Template name (required)") f.StringArrayVar(&templateImportFlags.files, "file", nil, "Template file mapping relpath=localpath (repeatable, required)") templateImportCmd.MarkFlagRequired("name") } func runTemplateImport(_ *cobra.Command, _ []string) error { if len(templateImportFlags.files) == 0 { return &exitError{code: 2, msg: "template import: at least one --file relpath=localpath is required"} } files, err := readFileMappings(templateImportFlags.files) if err != nil { return &exitError{code: 2, msg: "template import: " + err.Error()} } raw, err := newClient().ImportTemplate(templateImportFlags.name, files) if err != nil { return fmt.Errorf("template import: %w", err) } return printRaw(raw) } // readFileMappings parses "relpath=localpath" pairs and reads each local file // into the returned map[relpath]contents. func readFileMappings(pairs []string) (map[string]string, error) { out := make(map[string]string, len(pairs)) for _, p := range pairs { rel, local, ok := strings.Cut(p, "=") if !ok || rel == "" || local == "" { return nil, fmt.Errorf("invalid --file %q (want relpath=localpath)", p) } data, err := os.ReadFile(local) if err != nil { return nil, fmt.Errorf("read %s: %w", local, err) } out[rel] = string(data) } return out, nil } var templateRefreshCmd = &cobra.Command{ Use: "refresh", Short: "Refresh the template cache", RunE: runTemplateRefresh, } func runTemplateRefresh(_ *cobra.Command, _ []string) error { raw, err := newClient().RefreshTemplates() if err != nil { return fmt.Errorf("template refresh: %w", err) } if len(raw) == 0 { fmt.Println("Template cache refresh triggered.") return nil } return printRaw(raw) }