Skip to content

Commit

Permalink
Merge branch 'master' into CLOUDP-302737
Browse files Browse the repository at this point in the history
  • Loading branch information
jeroenvervaeke committed Mar 10, 2025
2 parents b317b12 + 7333676 commit 4189a28
Show file tree
Hide file tree
Showing 17 changed files with 155 additions and 109 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ bin/api-generator:
.PHONY: gen-api-commands
gen-api-commands: bin/api-generator ## Generate api commands
@echo "==> Generating api commands"
bin/api-generator --spec ./tools/api-generator/spec.yaml --overlay ./tools/api-generator/overlays > ./internal/api/commands.go
bin/api-generator --spec ./tools/api-generator/spec.yaml --overlay ./tools/api-generator/overlays --output-type commands > ./internal/api/commands.go

.PHONY: otel
otel: ## Generate code
Expand All @@ -126,7 +126,7 @@ gen-mocks: ## Generate mocks
.PHONY: gen-docs
gen-docs: ## Generate docs for atlascli commands
@echo "==> Generating docs"
go run -ldflags "$(LINKER_FLAGS)" ./tools/docs/main.go
go run -ldflags "$(LINKER_FLAGS)" ./tools/docs

.PHONY: build
build: ## Generate an atlas binary in ./bin
Expand Down
3 changes: 3 additions & 0 deletions internal/cli/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ func convertAPIToCobraCommand(command api.Command) (*cobra.Command, error) {
Aliases: command.Aliases,
Short: shortDescription,
Long: longDescription,
Annotations: map[string]string{
"operationId": command.OperationID,
},
PreRunE: func(cmd *cobra.Command, _ []string) error {
// Go through all commands that have not been touched/modified by the user and try to populate them from the users profile
// Common usecases:
Expand Down
File renamed without changes.
File renamed without changes.
54 changes: 43 additions & 11 deletions tools/api-generator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,55 @@ import (
"bytes"
"context"
_ "embed"
"errors"
"fmt"
"go/format"
"io"
"os"
"path/filepath"
"slices"
"strings"
"text/template"
"time"

"github.com/getkin/kin-openapi/openapi3"
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/api"
"github.com/speakeasy-api/openapi-overlay/pkg/overlay"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)

//go:embed commands.go.tmpl
var templateContent string
var commandsTemplateContent string

type OutputType string

const (
Commands OutputType = "commands"
Metadata OutputType = "metadata"
)

// Returns all possible values of OutputType.
func AllOutputTypes() []OutputType {
return []OutputType{Commands, Metadata}
}

func main() {
var (
specPath string
overlayPath string
specPath string
overlayPath string
outputTypeStr string
)

var rootCmd = &cobra.Command{
Use: "api-generator",
Short: "CLI which generates api command definitions from a OpenAPI spec",
RunE: func(command *cobra.Command, _ []string) error {
return run(command.Context(), specPath, overlayPath, command.OutOrStdout())
outputType := OutputType(outputTypeStr)
if !slices.Contains(AllOutputTypes(), outputType) {
return fmt.Errorf("'%s' is not a valid output type", outputType)
}

return run(command.Context(), specPath, overlayPath, outputType, command.OutOrStdout())
},
}

Expand All @@ -56,13 +75,15 @@ func main() {
_ = rootCmd.MarkFlagRequired("spec")
_ = rootCmd.MarkFlagFilename("spec")

rootCmd.Flags().StringVar(&outputTypeStr, "output-type", "", "Set output type [commands/metadata]")

if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

func run(ctx context.Context, specPath, overlayPath string, w io.Writer) error {
func run(ctx context.Context, specPath, overlayPath string, outputType OutputType, w io.Writer) error {
specFile, err := os.OpenFile(specPath, os.O_RDONLY, os.ModePerm)
if err != nil {
return err
Expand Down Expand Up @@ -94,7 +115,14 @@ func run(ctx context.Context, specPath, overlayPath string, w io.Writer) error {
overlayFiles = append(overlayFiles, overlayFile)
}

return convertSpecToAPICommands(ctx, specFile, overlayFiles, w)
switch outputType {
case Commands:
return convertSpecToAPICommands(ctx, specFile, overlayFiles, w)
case Metadata:
return errors.New("TODO")
default:
return fmt.Errorf("'%s' is not a valid outputType", outputType)
}
}

func applyOverlays(r io.Reader, overlayFiles []io.Reader) (io.Reader, error) {
Expand Down Expand Up @@ -134,6 +162,10 @@ func applyOverlays(r io.Reader, overlayFiles []io.Reader) (io.Reader, error) {
}

func convertSpecToAPICommands(ctx context.Context, r io.Reader, overlayFiles []io.Reader, w io.Writer) error {
return convertSpec(ctx, r, overlayFiles, w, specToCommands, commandsTemplateContent)
}

func convertSpec[T any](ctx context.Context, r io.Reader, overlayFiles []io.Reader, w io.Writer, mapper func(spec *openapi3.T) (T, error), templateContent string) error {
overlaySpec, err := applyOverlays(r, overlayFiles)
if err != nil {
return fmt.Errorf("failed to apply overlays, error: %w", err)
Expand All @@ -148,21 +180,21 @@ func convertSpecToAPICommands(ctx context.Context, r io.Reader, overlayFiles []i
return fmt.Errorf("spec validation failed, error: %w", err)
}

commands, err := specToCommands(spec)
commands, err := mapper(spec)
if err != nil {
return fmt.Errorf("failed convert spec to api commands: %w", err)
}

return writeCommands(w, commands)
return writeCommands(w, templateContent, commands)
}

func loadSpec(r io.Reader) (*openapi3.T, error) {
loader := openapi3.NewLoader()
return loader.LoadFromIoReader(r)
}

func writeCommands(w io.Writer, data api.GroupedAndSortedCommands) error {
tmpl, err := template.New("commands.go.tmpl").Funcs(template.FuncMap{
func writeCommands[T any](w io.Writer, templateContent string, data T) error {
tmpl, err := template.New("output").Funcs(template.FuncMap{
"currentYear": func() int {
return time.Now().UTC().Year()
},
Expand Down
18 changes: 13 additions & 5 deletions tools/api-generator/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package main
import (
"bytes"
"context"
"fmt"
"io"
"os"
"path/filepath"
Expand Down Expand Up @@ -47,13 +48,20 @@ func testSpec(t *testing.T, name, specPath string) {
overlays = []io.Reader{overlayFile}
}

buf := &bytes.Buffer{}
if err := convertSpecToAPICommands(context.Background(), specFile, overlays, buf); err != nil {
t.Fatalf("failed to convert spec into commmands, error: %s", err)
outputFunctions := map[OutputType]func(ctx context.Context, r io.Reader, overlayFiles []io.Reader, w io.Writer) error{
Commands: convertSpecToAPICommands,
}

if err := snapshotter.SnapshotWithName(name, buf.String()); err != nil {
t.Fatalf("unexpected result %s", err)
for outputType, outputTypeFunc := range outputFunctions {
buf := &bytes.Buffer{}

if err := outputTypeFunc(context.Background(), specFile, overlays, buf); err != nil {
t.Fatalf("failed to convert spec into commmands, error: %s", err)
}

if err := snapshotter.SnapshotWithName(fmt.Sprintf("%s-%s", name, outputType), buf.String()); err != nil {
t.Fatalf("unexpected result %s", err)
}
}
}

Expand Down
101 changes: 10 additions & 91 deletions tools/docs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,80 +17,13 @@ package main
import (
"log"
"os"
"strings"

"github.com/mongodb-labs/cobra2snooty"
pluginCmd "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/plugin"
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/root"
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/plugin"
"github.com/spf13/cobra"
)

const apiCommandName = "api"

func setDisableAutoGenTag(cmd *cobra.Command) {
cmd.DisableAutoGenTag = true
for _, cmd := range cmd.Commands() {
setDisableAutoGenTag(cmd)
}
}

func addExperimenalToAPICommands(cmd *cobra.Command) {
var apiCommand *cobra.Command
for _, subCommand := range cmd.Commands() {
if subCommand.Use == apiCommandName {
apiCommand = subCommand
}
}

if apiCommand == nil {
panic("api command not found!")
}

markExperimentalRecursively(apiCommand)
}

func markExperimentalRecursively(cmd *cobra.Command) {
cmd.Short = "`experimental <https://www.mongodb.com/docs/atlas/cli/current/command/atlas-api/>`_: " + cmd.Short

for _, subCommand := range cmd.Commands() {
markExperimentalRecursively(subCommand)
}
}

func updateAPICommandDescription(cmd *cobra.Command) {
var apiCommand *cobra.Command
for _, subCommand := range cmd.Commands() {
if subCommand.Use == apiCommandName {
apiCommand = subCommand
}
}

if apiCommand == nil {
panic("api command not found!")
}

updateLeafDescriptions(apiCommand)
}

func updateLeafDescriptions(cmd *cobra.Command) {
if len(cmd.Commands()) == 0 {
lines := strings.Split(cmd.Long, "\n")
// Replace last line if it contains the extected text: "For more information and examples, see: <AtlasCLI docs url>"
if strings.HasPrefix(lines[len(lines)-1], "For more information and examples, see: https://www.mongodb.com/docs/atlas/cli/current/command/") {
lines = lines[:len(lines)-1]
newLine := "For more information and examples, see the referenced API documentation linked above."
lines = append(lines, newLine)
}

cmd.Long = strings.Join(lines, "\n")
}

for _, subCommand := range cmd.Commands() {
updateLeafDescriptions(subCommand)
}
}

func addAdditionalLongText(cmd *cobra.Command) {
if additionalLongText, found := cmd.Annotations["DocsAdditionalLongText"]; found && additionalLongText != "" {
cmd.Long += "\n\n"
Expand All @@ -114,23 +47,21 @@ func main() {

atlasBuilder := root.Builder()

for _, cmd := range atlasBuilder.Commands() {
if plugin.IsPluginCmd(cmd) && !isFirstClassPlugin(cmd) {
atlasBuilder.RemoveCommand(cmd)
}
}

atlasBuilder.InitDefaultCompletionCmd()

setDisableAutoGenTag(atlasBuilder)
addExperimenalToAPICommands(atlasBuilder)
updateAPICommandDescription(atlasBuilder)
applyTransformations(atlasBuilder)
addAdditionalLongText(atlasBuilder)

if err := cobra2snooty.GenTreeDocs(atlasBuilder, "./docs/command"); err != nil {
log.Fatal(err)
}

if err := removeFirstClassPluginCommandDocs(); err != nil {
log.Fatal(err)
}
}

func removeFirstClassPluginCommandDocs() error {
firstClassPaths := make([]string, 0, len(pluginCmd.FirstClassPlugins))
for _, fcp := range pluginCmd.FirstClassPlugins {
cmd := fcp.Commands
Expand All @@ -141,21 +72,9 @@ func main() {
}

for _, filePath := range firstClassPaths {
err := os.Remove(filePath)
if err != nil {
log.Fatal(err)
}
}
}

func isFirstClassPlugin(command *cobra.Command) bool {
for _, fcp := range pluginCmd.FirstClassPlugins {
cmd := fcp.Commands
for _, c := range cmd {
if command.Name() == c.Name {
return true
}
if err := os.Remove(filePath); err != nil {
return err
}
}
return false
return nil
}
Loading

0 comments on commit 4189a28

Please sign in to comment.