diff --git a/.gitignore b/.gitignore index 27a7120..2887be0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /tcld +/app/docs /releases /proto .vscode/ diff --git a/app/docsgen/convert.go b/app/docsgen/convert.go new file mode 100644 index 0000000..85474be --- /dev/null +++ b/app/docsgen/convert.go @@ -0,0 +1,219 @@ +// Package docsgen is built to convert the existing +// cli.Commands to a docs gen model and generate tcld docs from that +package docsgen + +import ( + "fmt" + "sort" + "strings" + + "github.com/temporalio/tcld/app" + "github.com/urfave/cli/v2" +) + +type converter struct { + App *cli.App + CommandList []Command +} + +func ConvertCommands() (Commands, error) { + a, _ := app.NewApp(app.AppParams{}) + + c := converter{ + App: a, + CommandList: make([]Command, 0), + } + + err := c.addApp(asSlice(app.NewApp(app.AppParams{}))[0].(*cli.App)) + if err != nil { + return Commands{}, err + } + + err = c.addCommands(asSlice(app.NewNexusCommand(nil))[0].(app.CommandOut)) + if err != nil { + return Commands{}, err + } + + err = c.addCommands(asSlice(app.NewVersionCommand())[0].(app.CommandOut)) + if err != nil { + return Commands{}, err + } + err = c.addCommands(asSlice(app.NewAccountCommand(nil))[0].(app.CommandOut)) + if err != nil { + return Commands{}, err + } + err = c.addCommands(asSlice(app.NewNamespaceCommand(nil))[0].(app.CommandOut)) + if err != nil { + return Commands{}, err + } + err = c.addCommands(asSlice(app.NewUserCommand(nil))[0].(app.CommandOut)) + if err != nil { + return Commands{}, err + } + err = c.addCommands(asSlice(app.NewRequestCommand(nil))[0].(app.CommandOut)) + if err != nil { + return Commands{}, err + } + err = c.addCommands(asSlice(app.NewLoginCommand())[0].(app.CommandOut)) + if err != nil { + return Commands{}, err + } + err = c.addCommands(asSlice(app.NewLogoutCommand())[0].(app.CommandOut)) + if err != nil { + return Commands{}, err + } + err = c.addCommands(asSlice(app.NewCertificatesCommand())[0].(app.CommandOut)) + if err != nil { + return Commands{}, err + } + err = c.addCommands(asSlice(app.NewAPIKeyCommand(nil))[0].(app.CommandOut)) + if err != nil { + return Commands{}, err + } + err = c.addCommands(asSlice(app.NewFeatureCommand())[0].(app.CommandOut)) + if err != nil { + return Commands{}, err + } + err = c.addCommands(asSlice(app.NewServiceAccountCommand(nil))[0].(app.CommandOut)) + if err != nil { + return Commands{}, err + } + + commands := Commands{ + CommandList: c.CommandList, + } + + return commands, nil +} + +func (c *converter) addApp(a *cli.App) error { + options := make([]Option, 0) + for _, flag := range a.Flags { + option, err := flagToOption(flag) + if err != nil { + return err + } + options = append(options, option) + } + + c.CommandList = append(c.CommandList, Command{ + FullName: a.Name, + Summary: formatSummary(a.Usage, a.Name), + Description: formatCommandDescription(a.Description, ""), + Options: options, + }) + + return nil +} + +func formatSummary(v string, site string) string { + if len(v) == 0 { + site = strings.ReplaceAll(site, " ", "-") + v = "" + } + v = strings.TrimSuffix(v, ".") + return v +} + +func formatCommandDescription(v string, cmdName string) string { + v = formatDescription(v, cmdName) + + if strings.Contains(v, "These commands") { + v = strings.Replace(v, "These commands", fmt.Sprintf("The `%s` commands", cmdName), 1) + } else { + v = strings.Replace(v, "This command", fmt.Sprintf("The `%s` command", cmdName), 1) + } + + return v +} + +func formatDescription(v string, site string) string { + if len(v) == 0 { + site = strings.ReplaceAll(site, " ", "-") + v = "" + } + if !strings.HasSuffix(v, ".") { + v = v + "." + } + + return v +} + +func (c *converter) addCommands(co app.CommandOut) error { + return c.addCommandsVisitor(c.App.Name, co.Command) +} + +func (c *converter) addCommandsVisitor(prefix string, cmd *cli.Command) error { + name := fmt.Sprintf("%s %s", prefix, cmd.Name) + + options := make([]Option, 0) + for _, flag := range cmd.Flags { + option, err := flagToOption(flag) + if err != nil { + return err + } + options = append(options, option) + } + // alphabetize options + sort.Slice(options, func(i, j int) bool { + return options[i].Name < options[j].Name + }) + + c.CommandList = append(c.CommandList, Command{ + FullName: name, + Summary: formatSummary(cmd.Usage, name), + Description: formatCommandDescription(cmd.Description, name), + Short: getFirstAlias(cmd.Aliases), + Options: options, + }) + + for _, sc := range cmd.Subcommands { + err := c.addCommandsVisitor(name, sc) + if err != nil { + return err + } + } + + return nil +} + +func getFirstAlias(aliases []string) string { + alias := "" + if len(aliases) > 0 { + alias = aliases[0] + } + return alias +} + +func newOption(name string, usage string, required bool, aliases []string, optionType string) Option { + return Option{ + Name: name, + Description: formatDescription(usage, name), + Required: required, + Short: getFirstAlias(aliases), + Type: optionType, + } +} + +func flagToOption(flag cli.Flag) (Option, error) { + switch v := flag.(type) { + case *cli.StringFlag: + return newOption(v.Name, v.Usage, v.Required, v.Aliases, "string"), nil + case *cli.StringSliceFlag: + return newOption(v.Name, v.Usage, v.Required, v.Aliases, "string[]"), nil + case *cli.IntFlag: + return newOption(v.Name, v.Usage, v.Required, v.Aliases, "int"), nil + case *cli.PathFlag: + return newOption(v.Name, v.Usage, v.Required, v.Aliases, "string"), nil + case *cli.BoolFlag: + return newOption(v.Name, v.Usage, v.Required, v.Aliases, "bool"), nil + case *cli.TimestampFlag: + return newOption(v.Name, v.Usage, v.Required, v.Aliases, "timestamp"), nil + default: + return Option{}, fmt.Errorf("unknown flag type %#v", v) + } +} + +func asSlice(v ...interface{}) []interface{} { + return v +} diff --git a/app/docsgen/docs.go b/app/docsgen/docs.go new file mode 100644 index 0000000..dc9f838 --- /dev/null +++ b/app/docsgen/docs.go @@ -0,0 +1,111 @@ +// Package docsgen is built to convert the existing +// cli.Commands to a docs gen model and generate tcld docs from that +package docsgen + +import ( + "bytes" + "fmt" + "strings" +) + +type DocsFile struct { + FileName string +} + +func GenerateDocsFiles(commands Commands) (map[string][]byte, error) { + w := &docWriter{ + fileMap: make(map[string]*bytes.Buffer), + } + + // sort by parent command (activity, batch, etc) + for _, cmd := range commands.CommandList { + if err := cmd.writeDoc(w); err != nil { + return nil, fmt.Errorf("failed writing docs for command %s: %w", cmd.FullName, err) + } + } + + // Format and return + var finalMap = make(map[string][]byte) + for key, buf := range w.fileMap { + finalMap[key] = buf.Bytes() + } + return finalMap, nil +} + +type docWriter struct { + fileMap map[string]*bytes.Buffer +} + +func (c *Command) writeDoc(w *docWriter) error { + // If this is a root command, write a new file + if c.Depth == 1 { + w.writeCommand(c) + } + return nil +} + +func (w *docWriter) writeCommand(c *Command) { + fileName := c.FileName + w.fileMap[fileName] = &bytes.Buffer{} + w.fileMap[fileName].WriteString("---\n") + w.fileMap[fileName].WriteString("id: " + fileName + "\n") + w.fileMap[fileName].WriteString("title: " + c.FullName + " command reference\n") + w.fileMap[fileName].WriteString("sidebar_label: " + c.LeafName + "\n") + w.fileMap[fileName].WriteString("description: " + c.Summary + "\n") + w.fileMap[fileName].WriteString("slug: /cloud/tcld/" + c.LeafName + "\n") + w.fileMap[fileName].WriteString("toc_max_heading_level: 5\n") + w.fileMap[fileName].WriteString("keywords:\n") + w.fileMap[fileName].WriteString(" - " + "cli reference" + "\n") + w.fileMap[fileName].WriteString(" - " + "tcld" + "\n") + w.fileMap[fileName].WriteString("tags:\n") + w.fileMap[fileName].WriteString(" - " + "cli-reference" + "\n") + w.fileMap[fileName].WriteString(" - " + "tcld" + "\n") + w.fileMap[fileName].WriteString("---\n\n") + + w.writeCommandVisitor(c) +} + +func (w *docWriter) writeCommandVisitor(c *Command) { + if c.Depth > 1 { + prefix := strings.Repeat("#", c.Depth) + w.fileMap[c.FileName].WriteString(fmt.Sprintf("%s %s\n\n", prefix, c.LeafName)) + } + w.fileMap[c.FileName].WriteString(c.Description + "\n\n") + if len(c.Short) > 0 { + w.fileMap[c.FileName].WriteString("Alias: `" + c.Short + "`\n\n") + } + + if len(c.Children) == 0 { + w.writeCommandOptions(c) + } + + w.writeSubcommandToc(c) + + for _, c := range c.Children { + w.writeCommandVisitor(c) + } +} + +func (w *docWriter) writeSubcommandToc(c *Command) { + for _, c := range c.Children { + w.fileMap[c.FileName].WriteString(fmt.Sprintf("- [%s](#%s)\n", c.FullName, c.LeafName)) + } + if len(c.Children) > 0 { + w.fileMap[c.FileName].WriteString("\n") + } +} + +func (w *docWriter) writeCommandOptions(c *Command) { + if c.MaxChildDepth > 0 { + return + } + prefix := strings.Repeat("#", c.Depth+1) + + for _, option := range c.Options { + w.fileMap[c.FileName].WriteString(fmt.Sprintf("%s --%s\n\n", prefix, option.Name)) + w.fileMap[c.FileName].WriteString(option.Description + "\n\n") + if len(option.Short) > 0 { + w.fileMap[c.FileName].WriteString("Alias: `" + option.Short + "`\n\n") + } + } +} diff --git a/app/docsgen/enrich.go b/app/docsgen/enrich.go new file mode 100644 index 0000000..953c9b1 --- /dev/null +++ b/app/docsgen/enrich.go @@ -0,0 +1,263 @@ +// Package docsgen is built to convert the existing +// cli.Commands to a docs gen model and generate tcld docs from that +package docsgen + +import ( + _ "embed" + "fmt" + "sort" + "strings" +) + +func EnrichCommands(m Commands) (Commands, error) { + commandLookup := make(map[string]*Command) + + for i, optionSet := range m.OptionSets { + if err := m.OptionSets[i].processSection(); err != nil { + return Commands{}, fmt.Errorf("failed parsing option set section %q: %w", optionSet.Name, err) + } + } + + for i, command := range m.CommandList { + if err := m.CommandList[i].processSection(); err != nil { + return Commands{}, fmt.Errorf("failed parsing command section %q: %w", command.FullName, err) + } + + m.CommandList[i].Index = i + commandLookup[command.FullName] = &m.CommandList[i] + } + + var rootCommand *Command + + //populate parent and basic meta + for i, c := range m.CommandList { + commandLength := len(strings.Split(c.FullName, " ")) + if commandLength == 1 { + rootCommand = &m.CommandList[i] + continue + } + parentName := strings.Join(strings.Split(c.FullName, " ")[:commandLength-1], " ") + parent, ok := commandLookup[parentName] + if ok { + m.CommandList[i].Parent = &m.CommandList[parent.Index] + m.CommandList[i].Depth = len(strings.Split(c.FullName, " ")) - 1 + m.CommandList[i].FileName = strings.Split(c.FullName, " ")[1] + m.CommandList[i].LeafName = strings.Join(strings.Split(c.FullName, " ")[m.CommandList[i].Depth:], "") + } + } + + //populate children and base command + for _, c := range m.CommandList { + if c.Parent == nil { + continue + } + + //fmt.Printf("add child: %s\n", m.CommandList[c.Index].FullName) + m.CommandList[c.Parent.Index].Children = append(m.CommandList[c.Parent.Index].Children, &m.CommandList[c.Index]) + + base := &c + for base.Depth > 1 { + base = base.Parent + } + m.CommandList[c.Index].Base = &m.CommandList[base.Index] + } + + setMaxChildDepthVisitor(*rootCommand, &m) + + for i, c := range m.CommandList { + if c.Parent == nil { + continue + } + + subCommandStartDepth := 1 + if c.Base.MaxChildDepth > 2 { + subCommandStartDepth = 2 + } + + subCommandName := "" + if c.Depth >= subCommandStartDepth { + subCommandName = strings.Join(strings.Split(c.FullName, " ")[subCommandStartDepth:], " ") + } + + if len(subCommandName) == 0 && c.Depth == 1 { + // for operator base command to show up in tags, keywords, etc. + subCommandName = c.LeafName + } + + m.CommandList[i].SubCommandName = subCommandName + } + + // sorted ascending by full name of command (activity complete, batch list, etc) + sortChildrenVisitor(rootCommand) + + // pull flat list in same order as sorted children + m.CommandList = make([]Command, 0) + collectCommandVisitor(*rootCommand, &m) + + // option usages + optionUsages := getAllOptionUsages(m) + optionUsagesByOptionDescription := getOptionUsagesByOptionDescription(optionUsages) + m.Usages = Usages{ + OptionUsages: optionUsages, + OptionUsagesByOptionDescription: optionUsagesByOptionDescription, + } + + return m, nil +} + +func collectCommandVisitor(c Command, m *Commands) { + + m.CommandList = append(m.CommandList, c) + + for _, child := range c.Children { + collectCommandVisitor(*child, m) + } +} + +func sortChildrenVisitor(c *Command) { + sort.Slice(c.Children, func(i, j int) bool { + //option to put nested commands at end of the list + /* + if c.Children[i].MaxChildDepth != c.Children[j].MaxChildDepth { + return c.Children[i].MaxChildDepth < c.Children[j].MaxChildDepth + } + */ + + return c.Children[i].FullName < c.Children[j].FullName + }) + for _, command := range c.Children { + sortChildrenVisitor(command) + } +} + +func setMaxChildDepthVisitor(c Command, commands *Commands) int { + maxChildDepth := 0 + children := commands.CommandList[c.Index].Children + if len(children) > 0 { + for _, child := range children { + depth := setMaxChildDepthVisitor(*child, commands) + if depth > maxChildDepth { + maxChildDepth = depth + } + } + } + + commands.CommandList[c.Index].MaxChildDepth = maxChildDepth + return maxChildDepth + 1 +} + +func getAllOptionUsages(commands Commands) []OptionUsages { + // map[optionName]map[usageSite]OptionUsageSite + var optionUsageSitesMap = make(map[string]map[string]OptionUsageSite) + + // option sets + for i, optionSet := range commands.OptionSets { + usage := optionSet.Description + if len(usage) == 0 { + usage = optionSet.Name + } + + for j, option := range optionSet.Options { + _, found := optionUsageSitesMap[option.Name] + if !found { + optionUsageSitesMap[option.Name] = make(map[string]OptionUsageSite) + } + optionUsageSitesMap[option.Name][optionSet.Name] = OptionUsageSite{ + Option: commands.OptionSets[i].Options[j], + UsageSiteDescription: usage, + UsageSiteType: UsageTypeOptionSet, + } + } + } + + //command options + for i, cmd := range commands.CommandList { + usage := cmd.FullName + if len(usage) == 0 { + usage = cmd.FullName + } + + for j, option := range cmd.Options { + _, found := optionUsageSitesMap[option.Name] + if !found { + optionUsageSitesMap[option.Name] = make(map[string]OptionUsageSite) + } + optionUsageSitesMap[option.Name][cmd.FullName] = OptionUsageSite{ + Option: commands.CommandList[i].Options[j], + UsageSiteDescription: usage, + UsageSiteType: UsageTypeOptionSet, + } + } + } + + // all options + var allOptionUsages = make([]OptionUsages, 0) + + for optionName, usages := range optionUsageSitesMap { + option := OptionUsages{ + OptionName: optionName, + UsageSites: make([]OptionUsageSite, 0), + } + for _, usage := range usages { + option.UsageSites = append(option.UsageSites, usage) + } + allOptionUsages = append(allOptionUsages, option) + } + + sort.Slice(allOptionUsages, func(i, j int) bool { + return allOptionUsages[i].OptionName < allOptionUsages[j].OptionName + }) + + for u := range allOptionUsages { + sort.Slice(allOptionUsages[u].UsageSites, func(i, j int) bool { + return allOptionUsages[u].UsageSites[i].UsageSiteDescription < allOptionUsages[u].UsageSites[j].UsageSiteDescription + }) + } + + return allOptionUsages +} + +func getOptionUsagesByOptionDescription(allOptionUsages []OptionUsages) []OptionUsagesByOptionDescription { + out := make([]OptionUsagesByOptionDescription, len(allOptionUsages)) + + for i, optionUsages := range allOptionUsages { + out[i].OptionName = optionUsages.OptionName + + if len(optionUsages.UsageSites) == 1 { + usage := allOptionUsages[i].UsageSites[0] + out[i].Usages = make([]OptionUsageByOptionDescription, 1) + out[i].Usages[0].OptionDescription = usage.Option.Description + out[i].Usages[0].UsageSites = []OptionUsageSite{usage} + + continue + } + + // map[optionDescription]OptionUsageByOptionDescription + optionUsageByOptionDescriptionMap := make(map[string]OptionUsageByOptionDescription) + + // collate on option description in each usage site + for j, usage := range optionUsages.UsageSites { + _, found := optionUsageByOptionDescriptionMap[usage.Option.Description] + if !found { + optionUsageByOptionDescriptionMap[usage.Option.Description] = OptionUsageByOptionDescription{ + OptionDescription: usage.Option.Description, + UsageSites: make([]OptionUsageSite, 0), + } + } + u := optionUsageByOptionDescriptionMap[usage.Option.Description] + u.UsageSites = append(u.UsageSites, allOptionUsages[i].UsageSites[j]) + + // put all distinct option descriptions withing the option usages + optionUsageByOptionDescriptionMap[u.OptionDescription] = u + } + + out[i].Usages = make([]OptionUsageByOptionDescription, len(optionUsageByOptionDescriptionMap)) + j := 0 + for _, v := range optionUsageByOptionDescriptionMap { + out[i].Usages[j] = v + j++ + } + } + + return out +} diff --git a/app/docsgen/parse.go b/app/docsgen/parse.go new file mode 100644 index 0000000..9574afa --- /dev/null +++ b/app/docsgen/parse.go @@ -0,0 +1,229 @@ +// Package docsgen is built to convert the existing +// cli.Commands to a docs gen model and generate tcld docs from that +package docsgen + +import ( + _ "embed" + "fmt" + "regexp" + "slices" + "strings" +) + +type ( + // Option represents the structure of an option within option sets. + Option struct { + Name string `yaml:"name"` + Type string `yaml:"type"` + Description string `yaml:"description"` + Short string `yaml:"short,omitempty"` + Default string `yaml:"default,omitempty"` + Env string `yaml:"env,omitempty"` + Required bool `yaml:"required,omitempty"` + Aliases []string `yaml:"aliases,omitempty"` + EnumValues []string `yaml:"enum-values,omitempty"` + Experimental bool `yaml:"experimental,omitempty"` + } + + // Command represents the structure of each command in the commands map. + Command struct { + FullName string `yaml:"name"` + NamePath []string `yaml:",omitempty"` + Summary string `yaml:"summary"` + Description string `yaml:"description"` + Short string `yaml:"short,omitempty"` + DescriptionPlain string `yaml:",omitempty"` + DescriptionHighlighted string `yaml:",omitempty"` + HasInit bool `yaml:"has-init,omitempty"` + ExactArgs int `yaml:"exact-args,omitempty"` + MaximumArgs int `yaml:"maximum-args,omitempty"` + IgnoreMissingEnv bool `yaml:"ignores-missing-env,omitempty"` + Options []Option `yaml:"options,omitempty"` + OptionSets []string `yaml:"option-sets,omitempty"` + Docs Docs `yaml:"docs,omitempty"` + Index int `yaml:",omitempty"` + Base *Command `yaml:",omitempty"` + Parent *Command `yaml:",omitempty"` + Children []*Command `yaml:",omitempty"` + Depth int `yaml:",omitempty"` + FileName string `yaml:",omitempty"` + SubCommandName string `yaml:",omitempty"` + LeafName string `yaml:",omitempty"` + MaxChildDepth int `yaml:",omitempty"` + } + + // Docs represents docs-only information that is not used in CLI generation. + Docs struct { + Keywords []string `yaml:"keywords"` + DescriptionHeader string `yaml:"description-header"` + } + + // OptionSets represents the structure of option sets. + OptionSets struct { + Name string `yaml:"name"` + Description string `yaml:"description"` + Options []Option `yaml:"options"` + } + + // Commands represents the top-level structure holding commands and option sets. + Commands struct { + CommandList []Command `yaml:"commands"` + OptionSets []OptionSets `yaml:"option-sets"` + Usages Usages + } + + Usages struct { + OptionUsages []OptionUsages + OptionUsagesByOptionDescription []OptionUsagesByOptionDescription + } + + OptionUsages struct { + OptionName string + UsageSites []OptionUsageSite + } + + OptionUsageSite struct { + Option Option + UsageSiteDescription string + UsageSiteType UsageSiteType + } + + UsageSiteType string + + OptionUsagesByOptionDescription struct { + OptionName string + Usages []OptionUsageByOptionDescription + } + + OptionUsageByOptionDescription struct { + OptionDescription string + UsageSites []OptionUsageSite + } +) + +const ( + UsageTypeCommand UsageSiteType = "command" + UsageTypeOptionSet UsageSiteType = "optionset" +) + +var markdownLinkPattern = regexp.MustCompile(`\[(.*?)\]\((.*?)\)`) +var markdownBlockCodeRegex = regexp.MustCompile("```([\\s\\S]+?)```") +var markdownInlineCodeRegex = regexp.MustCompile("`([^`]+)`") + +const ansiReset = "\033[0m" +const ansiBold = "\033[1m" + +func (o OptionSets) processSection() error { + if o.Name == "" { + return fmt.Errorf("missing option set name") + } + + for i, option := range o.Options { + if err := o.Options[i].processSection(); err != nil { + return fmt.Errorf("failed parsing option '%v': %w", option.Name, err) + } + } + + return nil +} + +func (c *Command) processSection() error { + if c.FullName == "" { + return fmt.Errorf("missing command name") + } + c.NamePath = strings.Split(c.FullName, " ") + + if c.Summary == "" { + return fmt.Errorf("missing summary for command") + } + if c.Summary[len(c.Summary)-1] == '.' { + return fmt.Errorf("summary should not end in a '.'") + } + + if c.MaximumArgs != 0 && c.ExactArgs != 0 { + return fmt.Errorf("cannot have both maximum-args and exact-args") + } + + if c.Description == "" { + return fmt.Errorf("missing description for command: %s", c.FullName) + } + + /* + //TODO: we probably want to support roundtrip edits on tcld.yml that works + // in conjunction with convert.go OR to use `cli.Command.UsageText` + // for DescriptionHeader and maybe have a small set of hardcoded keywords + if len(c.NamePath) == 2 { + if c.Docs.Keywords == nil { + return fmt.Errorf("missing keywords for root command: %s", c.FullName) + } + if c.Docs.DescriptionHeader == "" { + return fmt.Errorf("missing description for root command: %s", c.FullName) + } + } + */ + + // Strip trailing newline for description + c.Description = strings.TrimSuffix(c.Description, "\n") + + // Strip links for long plain/highlighted + c.DescriptionPlain = markdownLinkPattern.ReplaceAllString(c.Description, "$1") + c.DescriptionHighlighted = c.DescriptionPlain + + // Highlight code for long highlighted + c.DescriptionHighlighted = markdownBlockCodeRegex.ReplaceAllStringFunc(c.DescriptionHighlighted, func(s string) string { + s = strings.Trim(s, "`") + s = strings.Trim(s, " ") + s = strings.Trim(s, "\n") + return ansiBold + s + ansiReset + }) + c.DescriptionHighlighted = markdownInlineCodeRegex.ReplaceAllStringFunc(c.DescriptionHighlighted, func(s string) string { + s = strings.Trim(s, "`") + return ansiBold + s + ansiReset + }) + + // Each option + for i, option := range c.Options { + if err := c.Options[i].processSection(); err != nil { + return fmt.Errorf("failed parsing option '%v': %w", option.Name, err) + } + } + + return nil +} + +func (o *Option) processSection() error { + if o.Name == "" { + return fmt.Errorf("missing option name") + } + + if o.Type == "" { + return fmt.Errorf("missing option type") + } + + if o.Description == "" { + return fmt.Errorf("missing description for option: %s", o.Name) + } + // Strip all newline for description and trailing whitespace + o.Description = strings.ReplaceAll(o.Description, "\n", " ") + o.Description = strings.TrimSuffix(o.Description, " ") + + // Check that description ends in a "." + if o.Description[len(o.Description)-1] != '.' { + return fmt.Errorf("description should end in a '.'") + } + + if o.Env != strings.ToUpper(o.Env) { + return fmt.Errorf("env variables must be in all caps") + } + + if len(o.EnumValues) != 0 { + if o.Type != "string-enum" && o.Type != "string-enum[]" { + return fmt.Errorf("enum-values can only specified for string-enum and string-enum[] types") + } + // Check default enum values + if o.Default != "" && !slices.Contains(o.EnumValues, o.Default) { + return fmt.Errorf("default value '%s' must be one of the enum-values options %s", o.Default, o.EnumValues) + } + } + return nil +} diff --git a/app/docsgen/tcld.yml b/app/docsgen/tcld.yml new file mode 100644 index 0000000..b20c0d0 --- /dev/null +++ b/app/docsgen/tcld.yml @@ -0,0 +1,1515 @@ +commands: + - name: tcld + summary: Temporal Cloud cli + description: . + options: + - name: server + type: string + description: saas-api server endpoint. + short: s + - name: config-dir + type: string + description: The config directory to use. + - name: auto_confirm + type: bool + description: Automatically confirm all prompts. + - name: api-key + type: string + description: The API Key used for authentication (preview). + - name: insecure + type: bool + description: Use an insecure transport for connection, recommended to avoid this option unless necessary. + - name: enable-debug-logs + type: bool + description: A flag to enable debug logs. + - name: tcld nexus + summary: Manage Nexus resources in Temporal Cloud + description: The `tcld nexus` commands manage Nexus resources in Temporal Cloud. + short: nxs + - name: tcld nexus endpoint + summary: Manage Nexus Endpoints in Temporal Cloud (EXPERIMENTAL) + description: The `tcld nexus endpoint` commands manage Nexus Endpoints in Temporal Cloud. + short: ep + - name: tcld nexus endpoint get + summary: Get a Nexus Endpoint by name (EXPERIMENTAL) + description: The `tcld nexus endpoint get` command gets a Nexus Endpoint configuration by name from the Cloud Account. + short: g + options: + - name: name + type: string + description: Endpoint name. + short: "n" + required: true + - name: tcld nexus endpoint list + summary: List Nexus Endpoints (EXPERIMENTAL) + description: The `tcld nexus endpoint list` command lists all Nexus Endpoint configurations on the Cloud Account. + short: l + - name: tcld nexus endpoint create + summary: Create a new Nexus Endpoint (EXPERIMENTAL) + description: |- + The `tcld nexus endpoint create` command creates a new Nexus Endpoint on the Cloud Account. + An endpoint name is used by in workflow code to invoke Nexus operations. + The endpoint target is a worker and `--target-namespace` and `--target-task-queue` must both be provided. + This will fail if an endpoint with the same name is already registered. + short: c + options: + - name: allow-namespace + type: string[] + description: Namespace that is allowed to call this endpoint (optional). + short: ans + - name: description + type: string + description: Endpoint description in markdown format (optional). + short: d + - name: description-file + type: string + description: Endpoint description file in markdown format (optional). + short: df + - name: name + type: string + description: Endpoint name. + short: "n" + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: target-namespace + type: string + description: Namespace in which a handler worker will be polling for Nexus tasks on. + short: tns + required: true + - name: target-task-queue + type: string + description: Task Queue in which a handler worker will be polling for Nexus tasks on. + short: ttq + required: true + - name: tcld nexus endpoint update + summary: Update an existing Nexus Endpoint (EXPERIMENTAL) + description: |- + The `tcld nexus endpoint update` command updates an existing Nexus Endpoint on the Cloud Account. + An endpoint name is used by in workflow code to invoke Nexus operations. + The endpoint target is a worker and `--target-namespace` and `--target-task-queue` must both be provided. + + The endpoint is patched leaving any existing fields for which flags are not provided as they were. + short: u + options: + - name: description + type: string + description: Endpoint description in markdown format (optional). + short: d + - name: description-file + type: string + description: Endpoint description file in markdown format (optional). + short: df + - name: name + type: string + description: Endpoint name. + short: "n" + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: target-namespace + type: string + description: Namespace in which a handler worker will be polling for Nexus tasks on (optional). + short: tns + - name: target-task-queue + type: string + description: Task Queue in which a handler worker will be polling for Nexus tasks on (optional). + short: ttq + - name: unset-description + type: bool + description: Unset endpoint description. + - name: tcld nexus endpoint allowed-namespace + summary: Allowed namespace operations for a Nexus Endpoint (EXPERIMENTAL) + description: The `tcld nexus endpoint allowed-namespace` commands manage the allowed namespaces for a Nexus Endpoint. + short: an + - name: tcld nexus endpoint allowed-namespace add + summary: Add allowed namespaces to a Nexus Endpoint (EXPERIMENTAL) + description: The `tcld nexus endpoint allowed-namespace add` command adds allowed namespaces to a Nexus Endpoint. + short: a + options: + - name: name + type: string + description: Endpoint name. + short: "n" + required: true + - name: namespace + type: string[] + description: Namespace that is allowed to call this endpoint. + short: ns + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld nexus endpoint allowed-namespace list + summary: List allowed namespaces of a Nexus Endpoint (EXPERIMENTAL) + description: The `tcld nexus endpoint allowed-namespace list` command lists the allowed namespaces of a Nexus Endpoint. + short: l + options: + - name: name + type: string + description: Endpoint name. + short: "n" + required: true + - name: tcld nexus endpoint allowed-namespace set + summary: Set allowed namespaces of a Nexus Endpoint (EXPERIMENTAL) + description: The `tcld nexus endpoint allowed-namespace set` command sets the allowed namespaces of a Nexus Endpoint. + short: s + options: + - name: name + type: string + description: Endpoint name. + short: "n" + required: true + - name: namespace + type: string[] + description: Namespace that is allowed to call this endpoint. + short: ns + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld nexus endpoint allowed-namespace remove + summary: Remove allowed namespaces from a Nexus Endpoint (EXPERIMENTAL) + description: The `tcld nexus endpoint allowed-namespace remove` command removes allowed namespaces from a Nexus Endpoint. + short: r + options: + - name: name + type: string + description: Endpoint name. + short: "n" + required: true + - name: namespace + type: string[] + description: Namespace that is allowed to call this endpoint. + short: ns + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld nexus endpoint delete + summary: Delete a Nexus Endpoint (EXPERIMENTAL) + description: |- + The `tcld nexus endpoint delete` command deletes a Nexus Endpoint on the Cloud Account. + . + short: d + options: + - name: name + type: string + description: Endpoint name. + short: "n" + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld version + summary: Version information + description: . + short: v + - name: tcld account + summary: Account operations + description: . + short: a + - name: tcld account get + summary: Get account information + description: . + short: g + - name: tcld account list-regions + summary: Lists all regions where the account can provision namespaces + description: . + short: l + - name: tcld account metrics + summary: Configures the metrics endpoint for the Temporal Cloud Account + description: . + short: m + - name: tcld account metrics enable + summary: Enables the metrics endpoint. CA Certificates *must* be configured prior to enabling the endpoint + description: . + - name: tcld account metrics disable + summary: Disables the metrics endpoint + description: . + - name: tcld account metrics accepted-client-ca + summary: Manages configuration of ca certificates for the external metrics endpoint + description: . + short: ca + - name: tcld account metrics accepted-client-ca add + summary: Add a new ca accepted client ca certificate + description: . + short: a + options: + - name: ca-certificate + type: string + description: The base64 encoded ca certificate. + short: c + - name: ca-certificate-file + type: string + description: The path to the ca pem file. + short: f + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld account metrics accepted-client-ca remove + summary: Remove existing certificates + description: . + short: r + options: + - name: ca-certificate + type: string + description: The base64 encoded ca certificate. + short: c + - name: ca-certificate-file + type: string + description: The path to the ca pem file. + short: f + - name: ca-certificate-fingerprint + type: string + description: The fingerprint of to the ca certificate. + short: fp + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld account metrics accepted-client-ca set + summary: Set the accepted client ca certificate + description: . + short: s + options: + - name: ca-certificate + type: string + description: The base64 encoded ca certificate. + short: c + - name: ca-certificate-file + type: string + description: The path to the ca pem file. + short: f + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld account metrics accepted-client-ca list + summary: List the accepted client ca certificates currently configured for the account metrics endpoint + description: . + short: l + - name: tcld namespace + summary: Namespace operations + description: . + short: "n" + - name: tcld namespace create + summary: Create a temporal namespace + description: . + short: c + options: + - name: auth-method + type: string + description: The authentication method to use for the namespace (e.g. 'mtls', 'api_key'). + - name: ca-certificate + type: string + description: The base64 encoded ca certificate. + short: c + - name: ca-certificate-file + type: string + description: The path to the ca pem file. + short: cf + - name: certificate-filter-file + type: string + description: 'Path to a JSON file that defines the certificate filters that will be added to the namespace. Sample JSON: { "filters": [ { "commonName": "test1" } ] }.' + short: cff + - name: certificate-filter-input + type: string + description: 'JSON that defines the certificate filters that will be added to the namespace. Sample JSON: { "filters": [ { "commonName": "test1" } ] }.' + short: cfi + - name: endpoint + type: string + description: The codec server endpoint to decode payloads for all users interacting with this Namespace, must be https. + short: e + - name: include-credentials + type: bool + description: Include cross-origin credentials. + short: ic + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: pass-access-token + type: bool + description: Pass the user access token to the remote endpoint. + short: pat + - name: region + type: string[] + description: Create namespace in specified regions; if multiple regions are selected, the first one will be the active region. See 'tcld account list-regions' to get a list of available regions for your account. + short: re + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: retention-days + type: int + description: The retention of the namespace in days. + short: rd + - name: search-attribute + type: string[] + description: 'Flag can be used multiple times; value must be "name=type"; valid types are: [Keyword Text Int Double Datetime Bool KeywordList].' + short: sa + - name: user-namespace-permission + type: string[] + description: 'Flag can be used multiple times; value must be "email=permission"; valid permissions are: [Admin Write Read].' + short: p + - name: tcld namespace add-region + summary: Add a new region to the Temporal Namespace + description: . + options: + - name: cloud-provider + type: string + description: 'The cloud provider of the region. Default: aws.' + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: region + type: string + description: New region to add to the namespace. + short: re + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: tcld namespace delete + summary: Delete a temporal namespace + description: . + short: d + options: + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld namespace list + summary: List all known namespaces + description: . + short: l + - name: tcld namespace get + summary: Get namespace information + description: . + short: g + options: + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: tcld namespace accepted-client-ca + summary: Manage client ca certificate used to verify client connections + description: . + short: ca + - name: tcld namespace accepted-client-ca list + summary: List the accepted client ca certificates currently configured for the namespace + description: . + short: l + options: + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: tcld namespace accepted-client-ca add + summary: Add a new ca accepted client ca certificate + description: . + short: a + options: + - name: ca-certificate + type: string + description: The base64 encoded ca certificate. + short: c + - name: ca-certificate-file + type: string + description: The path to the ca pem file. + short: f + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld namespace accepted-client-ca remove + summary: Remove existing certificates + description: . + short: r + options: + - name: ca-certificate + type: string + description: The base64 encoded ca certificate. + short: c + - name: ca-certificate-file + type: string + description: The path to the ca pem file. + short: f + - name: ca-certificate-fingerprint + type: string + description: The fingerprint of to the ca certificate. + short: fp + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld namespace accepted-client-ca set + summary: Set the accepted client ca certificate + description: . + short: s + options: + - name: ca-certificate + type: string + description: The base64 encoded ca certificate. + short: c + - name: ca-certificate-file + type: string + description: The path to the ca pem file. + short: f + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld namespace auth-method + summary: Manage the authentication method for the namespace + description: . + short: am + - name: tcld namespace auth-method set + summary: Set the authentication method for the namespace + description: . + options: + - name: auth-method + type: string + description: The authentication method used for the namespace (e.g. 'mtls', 'api_key'). + required: true + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld namespace auth-method get + summary: Retrieve the authentication method for namespace + description: . + options: + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: tcld namespace certificate-filters + summary: Manage optional certificate filters used by namespace to authorize client certificates based on distinguished name fields + description: . + short: cf + - name: tcld namespace certificate-filters import + summary: Sets the certificate filters on the namespace. Existing filters will be replaced + description: . + short: imp + options: + - name: certificate-filter-file + type: string + description: 'Path to a JSON file that defines the certificate filters that will be configured on the namespace. This will replace the existing filter configuration. Sample JSON: { "filters": [ { "commonName": "test1" } ] }.' + short: file + - name: certificate-filter-input + type: string + description: 'JSON that defines the certificate filters that will be configured on the namespace. This will replace the existing filter configuration. Sample JSON: { "filters": [ { "commonName": "test1" } ] }.' + short: input + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld namespace certificate-filters export + summary: Exports existing certificate filters on the namespace + description: . + short: exp + options: + - name: certificate-filter-file + type: string + description: Path to a JSON file where tcld will export the certificate filter configuration to. + short: file + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld namespace certificate-filters clear + summary: Clears all certificate filters on the namespace. Note that this will allow *any* client certificate that chains up to a configured CA in the bundle to connect to the namespace + description: . + short: c + options: + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld namespace certificate-filters add + summary: Adds additional certificate filters to the namespace + description: . + short: a + options: + - name: certificate-filter-file + type: string + description: 'Path to a JSON file that defines the certificate filters that will be added to the namespace. Sample JSON: { "filters": [ { "commonName": "test1" } ] }.' + short: file + - name: certificate-filter-input + type: string + description: 'JSON that defines the certificate filters that will be added to the namespace. Sample JSON: { "filters": [ { "commonName": "test1" } ] }.' + short: input + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld namespace update-codec-server + summary: Update codec server config used to decode encoded payloads through remote endpoint + description: . + short: ucs + options: + - name: endpoint + type: string + description: The codec server endpoint to decode payloads for all users interacting with this Namespace, must be https. + short: e + required: true + - name: include-credentials + type: bool + description: Include cross-origin credentials. + short: ic + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: pass-access-token + type: bool + description: Pass the user access token to the remote endpoint. + short: pat + - name: tcld namespace retention + summary: Manages configuration of the length of time (in days) a closed workflow will be preserved before deletion + description: . + short: r + - name: tcld namespace retention set + summary: Set the length of time (in days) a closed workflow will be preserved before deletion for a given namespace + description: . + short: s + options: + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: retention-days + type: int + description: The retention of the namespace in days. + short: rd + required: true + - name: tcld namespace retention get + summary: Retrieve the length of time (in days) a closed workflow will be preserved before deletion for a given namespace + description: . + short: g + options: + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: tcld namespace search-attributes + summary: Manage search attributes used by namespace + description: . + short: sa + - name: tcld namespace search-attributes add + summary: Add a new namespace custom search attribute + description: . + short: a + options: + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: search-attribute + type: string[] + description: 'Flag can be used multiple times; value must be "name=type"; valid types are: [Keyword Text Int Double Datetime Bool KeywordList].' + short: sa + required: true + - name: tcld namespace search-attributes rename + summary: Update the name of an existing custom search attribute + description: . + short: rn + options: + - name: existing-name + type: string + description: The name of an existing search attribute. + short: en + required: true + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: new-name + type: string + description: The new name for the search attribute. + short: nn + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld namespace failover + summary: Failover a temporal namespace + description: . + short: fo + options: + - name: cloud-provider + type: string + description: 'The cloud provider of the region. Default: aws.' + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: region + type: string + description: The region to failover to. + short: re + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: tcld namespace export + summary: Manage export + description: . + short: es + - name: tcld namespace export s3 + summary: Manage S3 export sink + description: . + - name: tcld namespace export s3 create + summary: Create export sink + description: . + short: c + options: + - name: kms-arn + type: string + description: 'Provide the ARN of the KMS key to use for encryption. Note: If the KMS ARN needs to be added or updated, user must create the IAM Role with KMS or modify the created IAM Role accordingly.' + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: region + type: string + description: The region to use for the request, if not set the server will use the namespace's region. + short: re + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: role-arn + type: string + description: Provide role arn for the IAM Role. + required: true + - name: s3-bucket-name + type: string + description: Provide the name of an AWS S3 bucket that Temporal will send closed workflow histories to. + required: true + - name: sink-name + type: string + description: Provide a name for the export sink. + required: true + - name: tcld namespace export s3 validate + summary: Validate export sink + description: . + short: v + options: + - name: kms-arn + type: string + description: 'Provide the ARN of the KMS key to use for encryption. Note: If the KMS ARN needs to be added or updated, user must create the IAM Role with KMS or modify the created IAM Role accordingly.' + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: region + type: string + description: The region to use for the request, if not set the server will use the namespace's region. + short: re + - name: role-arn + type: string + description: Provide role arn for the IAM Role. + required: true + - name: s3-bucket-name + type: string + description: Provide the name of an AWS S3 bucket that Temporal will send closed workflow histories to. + required: true + - name: sink-name + type: string + description: Provide a name for the export sink. + required: true + - name: tcld namespace export s3 update + summary: Update export sink + description: . + short: u + options: + - name: enabled + type: string + description: Whether export is enabled. + - name: kms-arn + type: string + description: 'Provide the ARN of the KMS key to use for encryption. Note: If the KMS ARN needs to be added or updated, user must create the IAM Role with KMS or modify the created IAM Role accordingly.' + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: role-arn + type: string + description: Provide role arn for the IAM Role. + - name: s3-bucket-name + type: string + description: Provide the name of an AWS S3 bucket that Temporal will send closed workflow histories to. + - name: sink-name + type: string + description: Provide a name for the export sink. + required: true + - name: tcld namespace export s3 get + summary: Get export sink + description: . + short: g + options: + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: sink-name + type: string + description: Provide a name for the export sink. + required: true + - name: tcld namespace export s3 delete + summary: Delete export sink + description: . + short: d + options: + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: sink-name + type: string + description: Provide a name for the export sink. + required: true + - name: tcld namespace export s3 list + summary: List export sinks + description: . + short: l + options: + - name: namespace + type: string + description: The namespace hosted on temporal cloud. + short: "n" + required: true + - name: page-size + type: int + description: The page size for list operations. + - name: page-token + type: string + description: The page token for list operations. + - name: tcld user + summary: User management operations + description: . + short: u + - name: tcld user list + summary: List users + description: . + short: l + options: + - name: namespace + type: string + description: List users that have permissions to the namespace. + short: "n" + - name: page-size + type: int + description: Page size for paging list users request. + short: s + - name: page-token + type: string + description: Page token for paging list users request. + short: p + - name: tcld user get + summary: Get user information + description: . + short: g + options: + - name: user-email + type: string + description: The user email address of the user. + short: e + - name: user-id + type: string + description: The user id. + short: id + - name: tcld user invite + summary: Invite users to Temporal Cloud + description: . + short: i + options: + - name: account-role + type: string + description: 'The account role to set on the user; valid types are: [Admin Developer Owner Read].' + short: ar + required: true + - name: namespace-permission + type: string[] + description: 'Flag can be used multiple times; value must be "namespace=permission"; valid types are: [Admin Read Write].' + short: p + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: user-email + type: string[] + description: The email address of the user, you can supply this flag multiple times to invite multiple users in a single request. + short: e + required: true + - name: tcld user resend-invite + summary: Resend invitation to a user on Temporal Cloud + description: . + short: ri + options: + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: user-email + type: string + description: The user email address of the user. + short: e + - name: user-id + type: string + description: The user id. + short: id + - name: tcld user delete + summary: Delete user from Temporal Cloud + description: . + short: d + options: + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: user-email + type: string + description: The user email address of the user. + short: e + - name: user-id + type: string + description: The user id. + short: id + - name: tcld user set-account-role + summary: Set account role for a user + description: . + short: sar + options: + - name: account-role + type: string + description: 'The account role to set on the user; valid types are: [Admin Developer Owner Read].' + short: ar + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: user-email + type: string + description: The user email address of the user. + short: e + - name: user-id + type: string + description: The user id. + short: id + - name: tcld user set-namespace-permissions + summary: Set entirely new set of namespace permissions for a user + description: . + short: snp + options: + - name: namespace-permission + type: string[] + description: 'Flag can be used multiple times; value must be "namespace=permission"; valid types are: [Admin Read Write].' + short: p + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: user-email + type: string + description: The user email address of the user. + short: e + - name: user-id + type: string + description: The user id. + short: id + - name: tcld request + summary: Manage asynchronous requests + description: . + short: r + - name: tcld request get + summary: Get the request status + description: . + short: g + options: + - name: request-id + type: string + description: The request-id of the asynchronous request. + short: r + required: true + - name: tcld login + summary: Login as user + description: . + short: l + options: + - name: audience + type: string + description: . + short: a + - name: client-id + type: string + description: . + short: id + - name: disable-pop-up + type: bool + description: disable browser pop-up. + - name: domain + type: string + description: . + short: d + - name: tcld logout + summary: Logout current user + description: . + short: lo + options: + - name: disable-pop-up + type: bool + description: disable browser pop-up. + - name: domain + type: string + description: . + short: d + - name: tcld generate-certificates + summary: Commands for generating certificate authority and end-entity TLS certificates + description: . + short: gen + - name: tcld generate-certificates certificate-authority-certificate + summary: Generate a certificate authority certificate + description: . + short: ca + options: + - name: ca-certificate-file + type: string + description: The path where the generated x509 certificate will be stored. + short: ca-cert + required: true + - name: ca-key-file + type: string + description: The path where the certificate's private key will be stored. + short: ca-key + required: true + - name: organization + type: string + description: The name of the organization. + short: org + required: true + - name: rsa-algorithm + type: bool + description: Generates a 4096-bit RSA keypair instead of an ECDSA P-384 keypair (the recommended default) for the certificate (optional). + short: rsa + - name: validity-period + type: string + description: 'The duration for which the certificate is valid for. example: 30d10h (30 days and 10 hrs).' + short: d + required: true + - name: tcld generate-certificates end-entity-certificate + summary: Generate an end-entity certificate + description: . + short: leaf + options: + - name: ca-certificate-file + type: string + description: The path of the x509 certificate for the certificate authority. + short: ca-cert + required: true + - name: ca-key-file + type: string + description: The path of the private key for the certificate authority. + short: ca-key + required: true + - name: certificate-file + type: string + description: The path where the generated x509 certificate will be stored. + short: cert + required: true + - name: common-name + type: string + description: The common name (optional). + - name: key-file + type: string + description: The path where the certificate's private key will be stored. + short: key + required: true + - name: organization + type: string + description: The name of the organization. + short: org + required: true + - name: organization-unit + type: string + description: The name of the organizational unit (optional). + - name: validity-period + type: string + description: 'The duration for which the end entity certificate is valid for. example: 30d10h (30 days and 10 hrs). By default the generated certificate expires 24 hours before the certificate authority expires (optional).' + short: d + - name: tcld apikey + summary: APIKey operations + description: . + short: ak + - name: tcld apikey create + summary: Create an apikey. Make sure to copy the secret or else you will not be able to retrieve it again + description: . + short: c + options: + - name: description + type: string + description: the description of the apikey. + short: desc + - name: duration + type: string + description: 'the duration from now when the apikey will expire, will be ignored if expiry flag is set, examples: ''2.5y'', ''30d'', ''4d12h''.' + short: d + - name: expiry + type: timestamp + description: 'the absolute timestamp (RFC3339) when the apikey will expire, example: ''2024-10-16T16:48:12-07:00''.' + short: e + - name: name + type: string + description: the display name of the apikey. + short: "n" + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: service-account-id + type: string + description: setting this flag will create an api key for a service account, not a user. + short: si + - name: tcld apikey get + summary: Get an apikey + description: . + short: g + options: + - name: id + type: string + description: The id of the apikey to get. + short: i + required: true + - name: tcld apikey list + summary: List apikeys + description: . + short: l + - name: tcld apikey delete + summary: Delete an apikey + description: . + short: d + options: + - name: id + type: string + description: The id of the apikey to delete. + short: i + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld apikey disable + summary: Disable an apikey + description: . + short: da + options: + - name: id + type: string + description: The id of the apikey to disable. + short: i + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld apikey enable + summary: Enable a disabled apikey + description: . + short: ea + options: + - name: id + type: string + description: The id of the apikey to enable. + short: i + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: tcld feature + summary: feature commands + description: . + short: f + - name: tcld feature toggle-gcp-sink + summary: switch gcp sink on/off + description: . + short: tgs + - name: tcld feature get + summary: get all feature flags Value + description: . + short: g + - name: tcld service-account + summary: Service Account management operations + description: . + short: sa + - name: tcld service-account create + summary: Create a service account + description: . + short: c + options: + - name: account-role + type: string + description: 'The account role to set on the service account; valid types are: [Admin Developer Owner Read].' + short: ar + required: true + - name: description + type: string + description: The service account description. + short: d + - name: name + type: string + description: The service account name. + short: "n" + - name: namespace-permission + type: string[] + description: 'Flag can be used multiple times; value must be "="; valid types are: [Admin Read Write].' + short: np + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: tcld service-account create-scoped + summary: Create a scoped service account (service account restricted to a single namespace) + description: . + short: cs + options: + - name: description + type: string + description: The service account description. + short: d + - name: name + type: string + description: The service account name. + short: "n" + - name: namespace-permission + type: string + description: 'Value must be "="; valid types are: [Admin Read Write].' + short: np + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: tcld service-account list + summary: List service accounts + description: . + short: l + options: + - name: page-size + type: int + description: Page size for paging list service accounts request. + short: s + - name: page-token + type: string + description: Page token for paging list service accounts request. + short: p + - name: tcld service-account get + summary: Get service account information + description: . + short: g + options: + - name: service-account-id + type: string + description: The service account id. + short: id + required: true + - name: tcld service-account update + summary: Update service account from Temporal Cloud + description: . + short: u + options: + - name: description + type: string + description: The service account description. + short: d + - name: name + type: string + description: The service account name. + short: "n" + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: service-account-id + type: string + description: The service account id. + short: id + required: true + - name: tcld service-account delete + summary: Delete service account from Temporal Cloud + description: . + short: d + options: + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: service-account-id + type: string + description: The service account id. + short: id + required: true + - name: tcld service-account set-account-role + summary: Set account role for a service account + description: . + short: sar + options: + - name: account-role + type: string + description: 'The account role to set on the service account; valid types are: [Admin Developer Owner Read].' + short: ar + required: true + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: service-account-id + type: string + description: The service account id. + short: id + required: true + - name: tcld service-account set-namespace-permissions + summary: Set entirely new set of namespace permissions for a service account + description: . + short: snp + options: + - name: namespace-permission + type: string[] + description: 'Flag can be used multiple times; value must be "namespace=permission"; valid types are: [Admin Read Write].' + short: p + - name: request-id + type: string + description: The request-id to use for the asynchronous operation, if not set the server will assign one (optional). + short: r + - name: resource-version + type: string + description: The resource-version (etag) to update from, if not set the cli will use the latest (optional). + short: v + - name: service-account-id + type: string + description: The service account id. + short: id + required: true +option-sets: [] +usages: + optionusages: [] + optionusagesbyoptiondescription: [] diff --git a/app/internal/cmd/gen-docs/main.go b/app/internal/cmd/gen-docs/main.go new file mode 100644 index 0000000..4ca99b6 --- /dev/null +++ b/app/internal/cmd/gen-docs/main.go @@ -0,0 +1,70 @@ +package main + +import ( + "fmt" + "log" + "os" + "path/filepath" + "runtime" + + "github.com/temporalio/tcld/app/docsgen" + "gopkg.in/yaml.v3" +) + +func main() { + if err := run(); err != nil { + log.Fatal(err) + } +} + +func run() error { + // Get commands dir + _, file, _, _ := runtime.Caller(0) + docsDir := filepath.Join(file, "../../../../docs/") + + err := os.MkdirAll(docsDir, os.ModePerm) + if err != nil { + log.Fatalf("Error creating directory: %v", err) + } + + // Convert existing CLI commands to docs gen model + cmds, err := docsgen.ConvertCommands() + if err != nil { + return fmt.Errorf("failed converting commands: %w", err) + } + + // Write docs gen YAML for easier reference + yamlData, err := yaml.Marshal(&cmds) + + if err != nil { + fmt.Printf("Error while Marshaling. %v", err) + } + + filePath := filepath.Join(file, "../../../../docsgen/tcld.yml") + err = os.WriteFile(filePath, yamlData, 0644) + if err != nil { + return fmt.Errorf("unable to write command yaml into %s", filePath) + } + + // Enrich commands + cmds, err = docsgen.EnrichCommands(cmds) + if err != nil { + return fmt.Errorf("failed enriching commands: %w", err) + } + + // Generate docs + b, err := docsgen.GenerateDocsFiles(cmds) + if err != nil { + return err + } + + // Write + for filename, content := range b { + filePath := filepath.Join(docsDir, filename+".mdx") + if err := os.WriteFile(filePath, content, 0644); err != nil { + return fmt.Errorf("failed writing file: %w", err) + } + } + + return nil +} diff --git a/app/nexus.go b/app/nexus.go index 52b1efc..0a7cd69 100644 --- a/app/nexus.go +++ b/app/nexus.go @@ -3,11 +3,12 @@ package app import ( "context" "fmt" + "os" + "github.com/temporalio/tcld/protogen/api/cloud/cloudservice/v1" "github.com/temporalio/tcld/protogen/api/cloud/nexus/v1" "github.com/temporalio/tcld/protogen/api/cloud/operation/v1" "github.com/urfave/cli/v2" - "os" ) type ( @@ -342,8 +343,10 @@ func NewNexusCommand(getNexusClientFn GetNexusClientFn) (CommandOut, error) { } return CommandOut{ Command: &cli.Command{ - Name: "nexus", - Aliases: []string{"nxs"}, + Name: "nexus", + Aliases: []string{"nxs"}, + Usage: "Manage Nexus resources in Temporal Cloud", + Description: "These commands manage Nexus resources in Temporal Cloud.", Before: func(ctx *cli.Context) error { var err error c, err = getNexusClientFn(ctx) @@ -351,9 +354,10 @@ func NewNexusCommand(getNexusClientFn GetNexusClientFn) (CommandOut, error) { }, Subcommands: []*cli.Command{ { - Name: "endpoint", - Aliases: []string{"ep"}, - Usage: "Commands for managing Nexus Endpoints (EXPERIMENTAL)", + Name: "endpoint", + Aliases: []string{"ep"}, + Usage: "Manage Nexus Endpoints in Temporal Cloud (EXPERIMENTAL)", + Description: "These commands manage Nexus Endpoints in Temporal Cloud.", Subcommands: []*cli.Command{ { Name: "get", @@ -477,14 +481,16 @@ func NewNexusCommand(getNexusClientFn GetNexusClientFn) (CommandOut, error) { }, }, { - Name: "allowed-namespace", - Aliases: []string{"an"}, - Usage: "Allowed namespace operations for a Nexus Endpoint (EXPERIMENTAL)", + Name: "allowed-namespace", + Aliases: []string{"an"}, + Usage: "Allowed namespace operations for a Nexus Endpoint (EXPERIMENTAL)", + Description: "These commands manage the allowed namespaces for a Nexus Endpoint", Subcommands: []*cli.Command{ { - Name: "add", - Aliases: []string{"a"}, - Usage: "Add allowed namespaces to a Nexus Endpoint (EXPERIMENTAL)", + Name: "add", + Aliases: []string{"a"}, + Usage: "Add allowed namespaces to a Nexus Endpoint (EXPERIMENTAL)", + Description: "This command adds allowed namespaces to a Nexus Endpoint", Flags: []cli.Flag{ endpointNameFlag, namespaceFlag, @@ -513,9 +519,10 @@ func NewNexusCommand(getNexusClientFn GetNexusClientFn) (CommandOut, error) { }, }, { - Name: "list", - Aliases: []string{"l"}, - Usage: "List allowed namespaces of a Nexus Endpoint (EXPERIMENTAL)", + Name: "list", + Aliases: []string{"l"}, + Usage: "List allowed namespaces of a Nexus Endpoint (EXPERIMENTAL)", + Description: "This command lists the allowed namespaces of a Nexus Endpoint", Flags: []cli.Flag{ endpointNameFlag, }, @@ -536,9 +543,10 @@ func NewNexusCommand(getNexusClientFn GetNexusClientFn) (CommandOut, error) { }, }, { - Name: "set", - Aliases: []string{"s"}, - Usage: "Set allowed namespaces of a Nexus Endpoint (EXPERIMENTAL)", + Name: "set", + Aliases: []string{"s"}, + Usage: "Set allowed namespaces of a Nexus Endpoint (EXPERIMENTAL)", + Description: "This command sets the allowed namespaces of a Nexus Endpoint", Flags: []cli.Flag{ endpointNameFlag, namespaceFlag, @@ -567,9 +575,10 @@ func NewNexusCommand(getNexusClientFn GetNexusClientFn) (CommandOut, error) { }, }, { - Name: "remove", - Aliases: []string{"r"}, - Usage: "Remove allowed namespaces from a Nexus Endpoint (EXPERIMENTAL)", + Name: "remove", + Aliases: []string{"r"}, + Usage: "Remove allowed namespaces from a Nexus Endpoint (EXPERIMENTAL)", + Description: "This command removes allowed namespaces from a Nexus Endpoint", Flags: []cli.Flag{ endpointNameFlag, namespaceFlag,