diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 956e2745..00000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..688b9aa0 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @cisco-lockhart/cdo-platform-team @cisco-lockhart/public-api-approvers \ No newline at end of file diff --git a/.github/workflows/golang.yml b/.github/workflows/golang.yml new file mode 100644 index 00000000..d1738d88 --- /dev/null +++ b/.github/workflows/golang.yml @@ -0,0 +1,87 @@ +name: build OpenAPI docs generation tool +on: + push: + branches: [ master, main ] + paths: + - scripts/golang/** + pull_request: + branches: [ master, main ] + paths: + - scripts/golang/** +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-tags: true + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + + - name: Install dependencies + working-directory: scripts/golang + run: | + go mod tidy + go install github.com/onsi/ginkgo/v2/ginkgo@latest + + - name: Run tests + working-directory: scripts/golang + run: | + ginkgo -r -v + + - name: Build Snapshots using Goreleaser + if: github.ref != 'refs/heads/main' + uses: goreleaser/goreleaser-action@v6 + with: + args: release --snapshot + workdir: 'scripts/golang' + + - name: Archive Snapshots + if: github.ref != 'refs/heads/main' + uses: actions/upload-artifact@v4 + with: + name: dist-snapshots + path: scripts/golang/dist/*.tar.gz + + # when branch is main, release a new version + - name: Bump version and push tag + id: tag_version + if: github.ref == 'refs/heads/main' + uses: mathieudutour/github-tag-action@v6.2 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + release: + runs-on: ubuntu-latest + needs: build + if: github.ref == 'refs/heads/main' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + + - name: Build using Goreleaser + uses: goreleaser/goreleaser-action@v6 + with: + args: release + workdir: 'scripts/golang' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: jfrog/setup-jfrog-cli@v4 + env: + JF_URL: ${{ vars.ARTIFACTORY_URL }} + JF_ACCESS_TOKEN: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index c2658d7d..0ac71739 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,7 @@ node_modules/ +.DS_Store +**/.idea +**/.DS_Store +scripts/golang/sdks/ +scripts/golang/openapitools.json +**/cloud-fw-mgr-api-docs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..f0a0b982 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,46 @@ +# How to Contribute + +Thank you for your interest in contributing to this repository. If you are not a Cisco employee, we +unfortunately cannot accept pull request submissions from you. However, we welcome you to open issues. + +If you are a Cisco employee who is working on a microservice that will become part of this repository, read on. + +# Adding a new microservice + +- Edit `cloud-fw-mgr-api-docs.config.yaml` in the root of this repo, and add the new microservice to + the `services` section. For example: + +```yaml + - name: "my-new-service" + url: "https://staging.manage.security.cisco.com/api/platform/my-new-service/v3/api-docs.yaml" +``` +- Edit `api-changelog.md` to add a short description of what APIs your new microservice provides. +- Submit a PR (please see commit message guidelines). +- You will need approval from the CODEOWNERS. See `.github/CODEOWNERS` to see who to request + approval from. +- Once your PR is approved, you will be (Cisco network only) be able to view the changes at + [https://devnetapps.cisco.com/docs/cisco-security-cloud-control/](https://devnetapps.cisco.com/docs/cisco-security-cloud-control/). +- To get it published to [https://developer.cisco.com/docs/cisco-security-cloud-control](https://developer.cisco.com/docs/cisco-security-cloud-control), + please work with the Cisco Devnet team or [Siddhu Warrier](mailto:siwarrie@cisco.com), as this is a manual process that will involve reviewing the APIs you have added to ensure + that they meet Cisco style guidelines. + +# Making API changes to an existing microservice + +## My change is backwards-incompatible + +To adhere to Cisco's policy around backwards compatibility, you **cannot** make backwards-incompatible changes to an existing microservice. +If you need to make a backwards-incompatible change, please work with [Jeremy Street](mailto:jerestre@cisco.com) and [Siddhu Warrier](mailto:siwarrie@cisco.com). + +## My change is backwards-compatible + +This can involve adding new APIs, or changing existing in APIs in a backwards-compatible manner. + +- Edit `api-changelog.md` to add a short description of what APIs your new microservice provides. +- Submit a PR (please see commit message guidelines). +- You will need approval from the CODEOWNERS. See `.github/CODEOWNERS` to see who to request + approval from. +- Once your PR is approved, you will be (Cisco network only) be able to view the changes at + [https://devnetapps.cisco.com/docs/cisco-security-cloud-control/](https://devnetapps.cisco.com/docs/cisco-security-cloud-control/). +- To get it published to [https://developer.cisco.com/docs/cisco-security-cloud-control](https://developer.cisco.com/docs/cisco-security-cloud-control), + please work with the Cisco Devnet team or [Siddhu Warrier](mailto:siwarrie@cisco.com), as this is a manual process that will involve reviewing the APIs you have added to ensure + that they meet Cisco style guidelines. \ No newline at end of file diff --git a/README.md b/README.md index 421f9706..7761b1eb 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,93 @@ # Welcome to the Cisco Security Cloud Control Public API Documentation -These docs are published to DevNet using PubHub. +These docs are published to DevNet using PubHub. See [https://pubhub.cisco.com/detail/5082] or contact siwarrie@cisco.com for details ## View your changes + Go to [https://devnetapps.cisco.com/docs/cisco-defense-orchestrator/]. + +# Contributions + +- PRs with changes to this file will only accepted from contributors who work for Cisco, who use + a Github account associated with their Cisco email address to create the PR. + +## Adding a new microservice + +- Edit `cloud-fw-mgr-api-docs.config.yaml` in the root of this repo, and add the new microservice to + the `services` section. For example: + +```yaml + - name: "my-new-service" + url: "https://staging.manage.security.cisco.com/api/platform/my-new-service/v3/api-docs.yaml" +``` +- Edit `api-changelog.md` to add a short description of what APIs your new microservice provides. +- Submit a PR (please see commit message guidelines). +- You will need approval from the CODEOWNERS. See `.github/CODEOWNERS` to see who to request + approval from. +- Once your PR is approved, you will be (Cisco network only) be able to view the changes at + [https://devnetapps.cisco.com/docs/cisco-security-cloud-control/](https://devnetapps.cisco.com/docs/cisco-security-cloud-control/). +- To get it published to [https://developer.cisco.com/docs/cisco-security-cloud-control](https://developer.cisco.com/docs/cisco-security-cloud-control), + please work with the Cisco Devnet team or [Siddhu Warrier](mailto:siwarrie@cisco.com), as this is a manual process that will involve reviewing the APIs you have added to ensure + that they meet Cisco style guidelines. + +## Making API changes to an existing microservice + +### My change is backwards-incompatible + +To adhere to Cisco's policy around backwards compatibility, you **cannot** make backwards-incompatible changes to an existing microservice. +If you need to make a backwards-incompatible change, please work with [Jeremy Street](mailto:jerestre@cisco.com) and [Siddhu Warrier](mailto:siwarrie@cisco.com). + +### My change is backwards-compatible + +This can involve adding new APIs, or changing existing in APIs in a backwards-compatible manner. + +- Edit `api-changelog.md` to add a short description of what APIs your new microservice provides. +- Submit a PR (please see commit message guidelines). +- You will need approval from the CODEOWNERS. See `.github/CODEOWNERS` to see who to request + approval from. +- Once your PR is approved, you will be (Cisco network only) be able to view the changes at + [https://devnetapps.cisco.com/docs/cisco-security-cloud-control/](https://devnetapps.cisco.com/docs/cisco-security-cloud-control/). +- To get it published to [https://developer.cisco.com/docs/cisco-security-cloud-control](https://developer.cisco.com/docs/cisco-security-cloud-control), + please work with the Cisco Devnet team or [Siddhu Warrier](mailto:siwarrie@cisco.com), as this is a manual process that will involve reviewing the APIs you have added to ensure + that they meet Cisco style guidelines. + +## PR guidelines + +All PR commits should follow +the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) format. +This will help us automate the release process, and we will set the version of the CLI on the basis +of the commit made. + +# The Golang CLI for API documentation generation + +## Pre-requisites + +- Install Golang: https://golang.org/doc/install +- Install cobra-cli: `go install github.com/spf13/cobra-cli@latest` +- Install pre-requisites: + +```shell +go run main.go install-pre-reqs +``` + +## Development + +### Add a new command + +```shell +cobra-cli add +``` + +### Testing + +All tests are written using [Ginkgo](https://github.com/onsi/ginkgo) +and [Gomega](https://github.com/onsi/gomega). + +To run your tests, use the following command: + +```shell +cd /path/to/scripts/golang +ginkgo -r +``` \ No newline at end of file diff --git a/cloud-fw-mgr-api-docs.config.yaml b/cloud-fw-mgr-api-docs.config.yaml new file mode 100644 index 00000000..341b11df --- /dev/null +++ b/cloud-fw-mgr-api-docs.config.yaml @@ -0,0 +1,40 @@ +services: + - name: "api-facade" + url: "https://staging.manage.security.cisco.com/api/platform/public-api/v3/api-docs.yaml" + - name: "msp-api" + url: "https://staging.manage.security.cisco.com/api/platform/msp-api/v3/api-docs.yaml" + - name: "device-upgrade" + url: "https://staging.manage.security.cisco.com/api/platform/device-upgrade/v3/api-docs.yaml" + - name: "object-service" + url: "https://staging.manage.security.cisco.com/api/platform/object-service/v3/api-docs.yaml" + - name: "transaction-service" + url: "https://staging.manage.security.cisco.com/api/platform/transaction-service/v3/api-docs.yaml" +info: + title: "Cisco Security Cloud Control API" + version: "1.5.0" + description: "Use the documentation to explore the endpoints Security Cloud Control has to offer" + contact: + name: "Cisco Security Cloud Control TAC" + email: "cdo.tac@cisco.com" +servers: + - url: https://edge.us.cdo.cisco.com/api/rest + description: US + - url: https://edge.eu.cdo.cisco.com/api/rest + description: EU + - url: https://edge.apj.cdo.cisco.com/api/rest + description: APJ + - url: https://edge.aus.cdo.cisco.com/api/rest + description: AUS + - url: https://edge.in.cdo.cisco.com/api/rest + description: IN + - url: https://edge.staging.cdo.cisco.com/api/rest + description: Staging + - url: https://edge.scale.cdo.cisco.com/api/rest + description: Scale + - url: https://edge.ci.cdo.cisco.com/api/rest + description: CI +securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT \ No newline at end of file diff --git a/scripts/golang/.gitignore b/scripts/golang/.gitignore new file mode 100644 index 00000000..cde01232 --- /dev/null +++ b/scripts/golang/.gitignore @@ -0,0 +1,2 @@ + +dist/ diff --git a/scripts/golang/.goreleaser.yaml b/scripts/golang/.goreleaser.yaml new file mode 100644 index 00000000..8de2bb30 --- /dev/null +++ b/scripts/golang/.goreleaser.yaml @@ -0,0 +1,60 @@ +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com + +# The lines below are called `modelines`. See `:help modeline` +# Feel free to remove those if you don't want/need to use them. +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj + +version: 1 + +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy + # you may remove this if you don't need go generate + - go generate ./... + +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + binary: cloud-fw-mgr-api-docs + +archives: + - format: tar.gz + # this name template makes the OS and Arch compatible with the results of `uname`. + name_template: >- + cloud-fw-mgr-api-docs_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + # use zip for windows archives + format_overrides: + - goos: windows + format: zip + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + +nfpms: + - + id: cloud-fw-mgr-api-docs + package_name: cloud-fw-mgr-api-docs + section: default + priority: extra + maintainer: "CDO Ops " + formats: + - deb + description: | + Cloud Firewall Manager API Docs + vendor: "Cisco Systems" + homepage: "https://www.cisco.com" diff --git a/scripts/golang/LICENSE b/scripts/golang/LICENSE new file mode 100644 index 00000000..e69de29b diff --git a/scripts/golang/cmd/generate_api_docs.go b/scripts/golang/cmd/generate_api_docs.go new file mode 100644 index 00000000..6a8085a8 --- /dev/null +++ b/scripts/golang/cmd/generate_api_docs.go @@ -0,0 +1,89 @@ +/* +Copyright © 2025 Cisco Systems +*/ +package cmd + +import ( + "fmt" + "github.com/cisco-lockhart/cloud-fw-mgr-api-docs/models" + "github.com/cisco-lockhart/cloud-fw-mgr-api-docs/services" + "github.com/pterm/pterm" + "gopkg.in/yaml.v3" + "os" + "path/filepath" + + "github.com/spf13/cobra" +) + +// generateCmd represents the generate command +var generateCmd = &cobra.Command{ + Use: "generate-api-docs", + Short: "Combine the API documentation from each microservice that constitutes the FCM API into one single API spec.", + Long: `This command downloads OpenAPI specs for all of the microservices that constitute + the Firewall public API and merges them together (including by removing schemas shared + between the microservices).`, + Run: func(cmd *cobra.Command, args []string) { + configUrl, err := cmd.Flags().GetString("config") + if err != nil { + pterm.Error.Println("Failed to get config URL", err) + os.Exit(1) + } + outputFile, err := cmd.Flags().GetString("output") + if err != nil { + pterm.Error.Println("Failed to get output file path", err) + os.Exit(1) + } + + spinner, _ := pterm.DefaultSpinner.Start("Loading configuration file...") + config, err := services.LoadConfig(configUrl) + if err != nil { + spinner.Fail(fmt.Sprintf("failed to load configuration file: %v\n", err)) + os.Exit(1) + } + spinner.Success(fmt.Sprintf("Configuration file loaded successfully: %s", configUrl)) + + serviceSpecs := make(map[string]*models.OpenAPI) + for _, service := range config.Services { + spinner, _ = pterm.DefaultSpinner.Start(fmt.Sprintf("Loading OpenAPI spec for service: %s...", service.Name)) + openApiSpec, err := services.LoadOpenApi(service.URL) + if err != nil { + spinner.Fail(fmt.Sprintf("failed to load OpenAPI spec for service: %s, error: %v\n", service.Name, err)) + os.Exit(1) + } + serviceSpecs[service.Name] = openApiSpec + spinner.Success(fmt.Sprintf("OpenAPI spec loaded successfully for service: %s", service.Name)) + } + + spinner, _ = pterm.DefaultSpinner.Start("Merging OpenAPI specs...") + mergedSpec, err := services.MergeOpenApiSpecs(serviceSpecs, config) + if err != nil { + spinner.Fail(fmt.Sprintf("failed to merge OpenAPI specs: %v\n", err)) + os.Exit(1) + } + spinner.Success("OpenAPI specs merged successfully") + + spinner, _ = pterm.DefaultSpinner.Start(fmt.Sprintf("Marshalling merged spec to YAML and writing to %s...", outputFile)) + yamlData, err := yaml.Marshal(mergedSpec) + if err != nil { + spinner.Fail(fmt.Sprintf("failed to marshal merged spec: %v\n", err)) + } + // Write the YAML data to a file + err = os.WriteFile(outputFile, yamlData, 0644) + if err != nil { + spinner.Fail(fmt.Sprintf("failed to write merged spec to file: %v\n", err)) + os.Exit(1) + } + spinner.Success("Merged spec marshalled to YAML successfully") + }, +} + +func init() { + rootCmd.AddCommand(generateCmd) + + currDir, err := os.Getwd() + if err != nil { + pterm.Error.Println("Failed to get current working directory", err) + } + defaultOutputFile := filepath.Join(currDir, "openapi.yaml") + generateCmd.Flags().StringP("output", "o", defaultOutputFile, fmt.Sprintf("Specify the output file path to write the merged OpenAPI spec to (default: %s)", defaultOutputFile)) +} diff --git a/scripts/golang/cmd/generate_postman_collection.go b/scripts/golang/cmd/generate_postman_collection.go new file mode 100644 index 00000000..5697430f --- /dev/null +++ b/scripts/golang/cmd/generate_postman_collection.go @@ -0,0 +1,50 @@ +/* +Copyright © 2025 Cisco Systems +*/ +package cmd + +import ( + "fmt" + "github.com/cisco-lockhart/cloud-fw-mgr-api-docs/services" + "github.com/pterm/pterm" + "github.com/spf13/cobra" + "os" + "path/filepath" +) + +var generatePostmanCollectionCmd = &cobra.Command{ + Use: "generate-postman-collection", + Short: "Generate a Postman collection from an OpenAPI spec", + Run: func(cmd *cobra.Command, args []string) { + spinner, _ := pterm.DefaultSpinner.Start("Generating Postman collection...") + openapiFile, err := cmd.Flags().GetString("openapi-file") + if err != nil { + spinner.Fail("Failed to get OpenAPI file", err) + os.Exit(1) + } + postmanCollectionFile, err := cmd.Flags().GetString("postman-collection-file") + if err != nil { + spinner.Fail("Failed to get Postman collection file", err) + os.Exit(1) + } + err = services.GeneratePostmanCollection(openapiFile, postmanCollectionFile) + if err != nil { + spinner.Fail("Failed to generate Postman collection", err) + os.Exit(1) + } + spinner.Success(fmt.Sprintf("Postman collection generated successfully at %s", postmanCollectionFile)) + }, +} + +func init() { + rootCmd.AddCommand(generatePostmanCollectionCmd) + currDir, err := os.Getwd() + if err != nil { + pterm.Error.Println("Failed to get current working directory", err) + } + + defaultInputFile := filepath.Join(currDir, "openapi.yaml") + defaultOutputFile := filepath.Join(currDir, "postman_collection.json") + generatePostmanCollectionCmd.Flags().StringP("openapi-file", "i", defaultInputFile, fmt.Sprintf("Specify the OpenAPI spec file to convert to a Postman collection", defaultInputFile)) + generatePostmanCollectionCmd.Flags().StringP("postman-collection-file", "o", defaultOutputFile, fmt.Sprintf("Specify the path to write the Postman collection to", defaultOutputFile)) +} diff --git a/scripts/golang/cmd/generate_sdks.go b/scripts/golang/cmd/generate_sdks.go new file mode 100644 index 00000000..602edbc8 --- /dev/null +++ b/scripts/golang/cmd/generate_sdks.go @@ -0,0 +1,115 @@ +/* +Copyright © 2025 NAME HERE +*/ +package cmd + +import ( + "github.com/cisco-lockhart/cloud-fw-mgr-api-docs/models" + "github.com/cisco-lockhart/cloud-fw-mgr-api-docs/services" + "github.com/pterm/pterm" + "gopkg.in/yaml.v3" + "os" + + "github.com/spf13/cobra" +) + +// generateSdksCmd represents the generateSdks command +var generateSdksCmd = &cobra.Command{ + Use: "generate-sdks ", + Short: "Generate SDKs for the Cloud Firewall Manager API", + Long: `Generates SDKs for the Cloud Firewall Manager API using the OpenAPI specification.`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + openAPISpecPath := args[0] + if _, err := os.Stat(openAPISpecPath); os.IsNotExist(err) { + pterm.Error.Printf("The specified OpenAPI spec file does not exist: %s\n", openAPISpecPath) + os.Exit(1) + } + data, err := os.ReadFile(openAPISpecPath) + if err != nil { + pterm.Error.Println("Failed to read OpenAPI spec file", err) + os.Exit(1) + } + openApiSpec := models.OpenAPI{} + if err = yaml.Unmarshal(data, &openApiSpec); err != nil { + pterm.Error.Println("Failed to unmarshal OpenAPI spec", err) + os.Exit(1) + } + useLocalInstallation, err := cmd.Flags().GetBool("use-local-installation") + if err != nil { + pterm.Error.Println("Failed to get publish flag", err) + os.Exit(1) + } + + version, err := generatePythonSdk(openAPISpecPath, useLocalInstallation, &openApiSpec) + if err != nil { + pterm.Error.Println("Failed to generate Python SDK", err) + os.Exit(1) + } + + shouldPublish, err := cmd.Flags().GetBool("publish") + if err != nil { + pterm.Error.Println("Failed to get publish flag", err) + os.Exit(1) + } + if shouldPublish { + pypiToken, err := cmd.Flags().GetString("pypi-token") + if err != nil { + pterm.Error.Println("Failed to get PyPI token", err) + os.Exit(1) + } + // golang cobra does not support nil flag values + publishPythonSdk(pypiToken, *version) + } + }, +} + +func generatePythonSdk(openApiFile string, useLocalInstallation bool, openApiSpec *models.OpenAPI) (*string, error) { + // Generate Python SDK + version, err := services.GetCurrentVersion(openApiSpec.Info.Version) + if err != nil { + pterm.Error.Println("Failed to get current version", err) + return nil, err + } + pterm.Info.Printf("Generating Python SDK with version %s\n", *version) + spinner, _ := pterm.DefaultSpinner.Start("Generating Python SDK...") + err = services.GeneratePythonSdk(openApiFile, *version, useLocalInstallation) + if err != nil { + spinner.Fail("Error generating Python SDK", err) + os.Exit(1) + } + spinner.Success("Python SDK generated successfully") + + return version, nil +} + +func publishPythonSdk(pypiToken string, version string) { + // Publish Python SDK + spinner, _ := pterm.DefaultSpinner.Start("Publishing Python SDK...") + var tokenPtr *string + if pypiToken != "" { + tokenPtr = &pypiToken + } + err := services.PublishPythonSdk(tokenPtr, version) + if err != nil { + spinner.Fail("Error publishing Python SDK", err) + os.Exit(1) + } + spinner.Success("Python SDK published successfully") +} + +func init() { + rootCmd.AddCommand(generateSdksCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // generateSdksCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + generateSdksCmd.Flags().BoolP("publish", "p", false, "Publish package to repositories after generation") + generateSdksCmd.Flags().BoolP("use-local-installation", "l", false, "Install @openapitools/openapi-generator-cli locally and use local installation instead of global installation. This is required for Jenins agents, where the global installation fails.") + generateSdksCmd.Flags().StringP("pypi-token", "t", "", "PyPI token to use for publishing the package. If not specified, it will pull the token from AWS secrets manager.") +} diff --git a/scripts/golang/cmd/install_pre_reqs.go b/scripts/golang/cmd/install_pre_reqs.go new file mode 100644 index 00000000..54345efc --- /dev/null +++ b/scripts/golang/cmd/install_pre_reqs.go @@ -0,0 +1,79 @@ +/* +Copyright © 2025 Cisco Systems +*/ +package cmd + +import ( + "github.com/cisco-lockhart/cloud-fw-mgr-api-docs/services" + "github.com/pterm/pterm" + "os" + "os/exec" + "runtime" + + "github.com/spf13/cobra" +) + +var nodePackagesToInstall = []string{"openapi-to-postmanv2", "@openapitools/openapi-generator-cli"} +var homebrewPackages = []string{"pipx"} +var pipxPackages = []string{"twine", "wheel"} + +// installPreReqsCmd represents the installPreReqs command +var installPreReqsCmd = &cobra.Command{ + Use: "install-pre-reqs", + Short: "Install pre-requisites for the CLI tool", + Long: `Installs all the pre-requisites (such as NPM etc) to allow the CLI tool to generate SDKs etc. Only supports MacOS at the moment. Requires npm to be installed and in your path.`, + Run: func(cmd *cobra.Command, args []string) { + if runtime.GOOS != "darwin" { + pterm.Error.Println("This command is only supported on MacOS at the moment.") + return + } + npmVersionCmd := exec.Command("npm", "-v") + _, err := npmVersionCmd.Output() + if err != nil { + pterm.Error.Println("npm is not installed.") + os.Exit(1) + } + for _, nodePackage := range nodePackagesToInstall { + spinner, _ := pterm.DefaultSpinner.Start("Installing node package " + nodePackage) + err := services.InstallNodePackage(nodePackage) + if err != nil { + spinner.Fail("Error installing node package", nodePackage) + os.Exit(1) + } + spinner.Success("Node package " + nodePackage + " installed successfully") + } + + for _, brewPackage := range homebrewPackages { + spinner, _ := pterm.DefaultSpinner.Start("Installing brew package " + brewPackage) + if err := services.InstallHomebrewPackage("pipx"); err != nil { + spinner.Fail("Error installing brew package", brewPackage) + os.Exit(1) + } + spinner.Success("Homebrew package " + brewPackage + " installed successfully") + } + for _, pipxPackage := range pipxPackages { + spinner, _ := pterm.DefaultSpinner.Start("Installing pipx package " + pipxPackage) + if err := services.InstallPipxPackage(pipxPackage); err != nil { + spinner.Fail("Error installing pipx package", pipxPackage) + os.Exit(1) + } + spinner.Success("Pipx package " + pipxPackage + " installed successfully") + } + + pterm.Success.Println("All pre-requisites installed successfully.") + }, +} + +func init() { + rootCmd.AddCommand(installPreReqsCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // installPreReqsCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // installPreReqsCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/scripts/golang/cmd/root.go b/scripts/golang/cmd/root.go new file mode 100644 index 00000000..a35f7e4a --- /dev/null +++ b/scripts/golang/cmd/root.go @@ -0,0 +1,29 @@ +/* +Copyright © 2025 Cisco Systems +*/ +package cmd + +import ( + "github.com/spf13/cobra" + "os" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "cloud-fw-mgr-api-docs", + Short: "CLI tool to generate API documentation and SDKs for Cloud Firewall Manager", + Long: `This tool provides commands to generate API documentation and SDKs for Cloud Firewall Manager.`, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + rootCmd.PersistentFlags().StringP("config", "c", "https://raw.githubusercontent.com/cisco-lockhart/cdo-public-api-docs/refs/heads/main/cloud-fw-mgr-api-docs.config.yaml", "config URL") +} diff --git a/scripts/golang/go.mod b/scripts/golang/go.mod new file mode 100644 index 00000000..73d1ca18 --- /dev/null +++ b/scripts/golang/go.mod @@ -0,0 +1,49 @@ +module github.com/cisco-lockhart/cloud-fw-mgr-api-docs + +go 1.23.1 + +require ( + github.com/Masterminds/semver/v3 v3.3.1 + github.com/aws/aws-sdk-go-v2/config v1.28.8 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.9 + github.com/onsi/ginkgo/v2 v2.22.2 + github.com/onsi/gomega v1.36.2 + github.com/pterm/pterm v0.12.80 + github.com/spf13/cobra v1.8.1 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + atomicgo.dev/cursor v0.2.0 // indirect + atomicgo.dev/keyboard v0.2.9 // indirect + atomicgo.dev/schedule v0.1.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.32.7 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.49 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.33.4 // indirect + github.com/aws/smithy-go v1.22.1 // indirect + github.com/containerd/console v1.0.3 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect + github.com/gookit/color v1.5.4 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/lithammer/fuzzysearch v1.1.8 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.28.0 // indirect +) diff --git a/scripts/golang/go.sum b/scripts/golang/go.sum new file mode 100644 index 00000000..eb54d144 --- /dev/null +++ b/scripts/golang/go.sum @@ -0,0 +1,177 @@ +atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= +atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ= +atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw= +atomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= +atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= +atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= +atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= +atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= +github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= +github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= +github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= +github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k= +github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI= +github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= +github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= +github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= +github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= +github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= +github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= +github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw= +github.com/aws/aws-sdk-go-v2 v1.32.7/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= +github.com/aws/aws-sdk-go-v2/config v1.28.8 h1:4nUeC9TsZoHm9GHlQ5tnoIklNZgISXXVGPKP5/CS0fk= +github.com/aws/aws-sdk-go-v2/config v1.28.8/go.mod h1:2C+fhFxnx1ymomFjj5NBUc/vbjyIUR7mZ/iNRhhb7BU= +github.com/aws/aws-sdk-go-v2/credentials v1.17.49 h1:+7u6eC8K6LLGQwWMYKHSsHAPQl+CGACQmnzd/EPMW0k= +github.com/aws/aws-sdk-go-v2/credentials v1.17.49/go.mod h1:0SgZcTAEIlKoYw9g+kuYUwbtUUVjfxnR03YkCOhMbQ0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 h1:kqOrpojG71DxJm/KDPO+Z/y1phm1JlC8/iT+5XRmAn8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22/go.mod h1:NtSFajXVVL8TA2QNngagVZmUtXciyrHOt7xgz4faS/M= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 h1:I/5wmGMffY4happ8NOCuIUEWGUvvFp5NSeQcXl9RHcI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26/go.mod h1:FR8f4turZtNy6baO0KJ5FJUmXH/cSkI9fOngs0yl6mA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 h1:zXFLuEuMMUOvEARXFUVJdfqZ4bvvSgdGRq/ATcrQxzM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26/go.mod h1:3o2Wpy0bogG1kyOPrgkXA8pgIfEEv0+m19O9D5+W8y8= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 h1:8eUsivBQzZHqe/3FE+cqwfH+0p5Jo8PFM/QYQSmeZ+M= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7/go.mod h1:kLPQvGUmxn/fqiCrDeohwG33bq2pQpGeY62yRO6Nrh0= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.9 h1:cyAt42tqeT06dSOziRmdt91iDylhP7aA6YJGeOkNe5g= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.9/go.mod h1:By/yiMzR0yfhPaqRWE3GrT9B/Z6871z1GfWGc+vf4Y8= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 h1:CvuUmnXI7ebaUAhbJcDy9YQx8wHR69eZ9I7q5hszt/g= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.8/go.mod h1:XDeGv1opzwm8ubxddF0cgqkZWsyOtw4lr6dxwmb6YQg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 h1:F2rBfNAL5UyswqoeWv9zs74N/NanhK16ydHW1pahX6E= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7/go.mod h1:JfyQ0g2JG8+Krq0EuZNnRwX0mU0HrwY/tG6JNfcqh4k= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.4 h1:EzofOvWNMtG9ELt9mPOJjLYh1hz6kN4f5hNCyTtS7Hg= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.4/go.mod h1:5Gn+d+VaaRgsjewpMvGazt0WfcFO+Md4wLOuBfGR9Bc= +github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= +github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= +github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= +github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= +github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= +github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= +github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= +github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE= +github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU= +github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= +github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= +github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= +github.com/pterm/pterm v0.12.80 h1:mM55B+GnKUnLMUSqhdINe4s6tOuVQIetQ3my8JGyAIg= +github.com/pterm/pterm v0.12.80/go.mod h1:c6DeF9bSnOSeFPZlfs4ZRAFcf5SCoTwvwQ5xaKGQlHo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= +golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/scripts/golang/golang_suite_test.go b/scripts/golang/golang_suite_test.go new file mode 100644 index 00000000..76ad298e --- /dev/null +++ b/scripts/golang/golang_suite_test.go @@ -0,0 +1,13 @@ +package main_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestGolang(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Golang Suite") +} diff --git a/scripts/golang/main.go b/scripts/golang/main.go new file mode 100644 index 00000000..8a533818 --- /dev/null +++ b/scripts/golang/main.go @@ -0,0 +1,10 @@ +/* +Copyright © 2025 Cisco Systems +*/ +package main + +import "github.com/cisco-lockhart/cloud-fw-mgr-api-docs/cmd" + +func main() { + cmd.Execute() +} diff --git a/scripts/golang/models/config.go b/scripts/golang/models/config.go new file mode 100644 index 00000000..1f462d3b --- /dev/null +++ b/scripts/golang/models/config.go @@ -0,0 +1,29 @@ +package models + +type Service struct { + Name string `yaml:"name"` + URL string `yaml:"url"` +} +type Info struct { + Title string `yaml:"title"` + Version string `yaml:"version"` + Description string `yaml:"description"` + Contact Contact `yaml:"contact"` +} + +type Contact struct { + Name string `yaml:"name"` + Email string `yaml:"email"` +} + +type Server struct { + URL string `yaml:"url"` + Description string `yaml:"description"` +} + +type Config struct { + Services []Service `yaml:"services"` + Info Info `yaml:"info"` + Servers []Server `yaml:"servers"` + SecuritySchemes map[string]interface{} `yaml:"securitySchemes"` +} diff --git a/scripts/golang/models/openapi.go b/scripts/golang/models/openapi.go new file mode 100644 index 00000000..d27b2c61 --- /dev/null +++ b/scripts/golang/models/openapi.go @@ -0,0 +1,19 @@ +package models + +type Components struct { + Schemas map[string]interface{} `yaml:"schemas,omitempty"` + Responses map[string]interface{} `yaml:"responses,omitempty"` + SecuritySchemes map[string]interface{} `yaml:"securitySchemes,omitempty"` +} + +type OpenAPI struct { + OpenAPI string `yaml:"openapi"` + Info Info `yaml:"info,omitempty"` + Security []map[string]interface{} `yaml:"security,omitempty"` + Tags []map[string]interface{} `yaml:"tags,omitempty"` + Paths map[string]interface{} `yaml:"paths,omitempty"` + ExternalDocs map[string]interface{} `yaml:"externalDocs,omitempty"` + Servers []Server `yaml:"servers,omitempty"` + Components Components `yaml:"components,omitempty"` + Definitions map[string]interface{} `yaml:"definitions,omitempty"` +} diff --git a/scripts/golang/models/pypi_version.go b/scripts/golang/models/pypi_version.go new file mode 100644 index 00000000..e0bd37dd --- /dev/null +++ b/scripts/golang/models/pypi_version.go @@ -0,0 +1,42 @@ +package models + +type info struct { + Author string `json:"author"` + AuthorEmail string `json:"author_email"` + BugtrackURL *string `json:"bugtrack_url"` + Classifiers []string `json:"classifiers"` + Description string `json:"description"` + DescriptionContentType string `json:"description_content_type"` + DocsURL *string `json:"docs_url"` + DownloadURL *string `json:"download_url"` + Downloads struct { + LastDay int `json:"last_day"` + LastMonth int `json:"last_month"` + LastWeek int `json:"last_week"` + } `json:"downloads"` + Dynamic *string `json:"dynamic"` + HomePage *string `json:"home_page"` + Keywords string `json:"keywords"` + License *string `json:"license"` + LicenseExpression *string `json:"license_expression"` + LicenseFiles *string `json:"license_files"` + Maintainer *string `json:"maintainer"` + MaintainerEmail *string `json:"maintainer_email"` + Name string `json:"name"` + PackageURL string `json:"package_url"` + Platform *string `json:"platform"` + ProjectURL string `json:"project_url"` + ProjectURLs *string `json:"project_urls"` + ProvidesExtra *string `json:"provides_extra"` + ReleaseURL string `json:"release_url"` + RequiresDist []string `json:"requires_dist"` + RequiresPython *string `json:"requires_python"` + Summary string `json:"summary"` + Version string `json:"version"` + Yanked bool `json:"yanked"` + YankedReason *string `json:"yanked_reason"` +} + +type PypiVersionInfo struct { + Info info `json:"info"` +} diff --git a/scripts/golang/services/command_executor.go b/scripts/golang/services/command_executor.go new file mode 100644 index 00000000..db49f2cc --- /dev/null +++ b/scripts/golang/services/command_executor.go @@ -0,0 +1,11 @@ +package services + +import "os/exec" + +type CommandExecutor interface { + Output() ([]byte, error) +} + +var ExecCommand = func(name string, args ...string) CommandExecutor { + return exec.Command(name, args...) +} diff --git a/scripts/golang/services/config_reader_service.go b/scripts/golang/services/config_reader_service.go new file mode 100644 index 00000000..3d62d960 --- /dev/null +++ b/scripts/golang/services/config_reader_service.go @@ -0,0 +1,42 @@ +package services + +import ( + "fmt" + "github.com/cisco-lockhart/cloud-fw-mgr-api-docs/models" + "gopkg.in/yaml.v3" + "io" + "net/http" +) + +func LoadConfig(url string) (*models.Config, error) { + // Load the configuration file + resp, err := http.Get(url) + if err != nil { + panic(err) + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + panic(err) + } + }(resp.Body) + + // Check if the request was successful + if resp.StatusCode != http.StatusOK { + panic(fmt.Sprintf("failed to fetch URL: %s, status code: %d", url, resp.StatusCode)) + } + + // Read the response body + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + config := models.Config{} + err = yaml.Unmarshal(data, &config) + if err != nil { + return nil, err + } + + return &config, nil +} diff --git a/scripts/golang/services/config_reader_service_test.go b/scripts/golang/services/config_reader_service_test.go new file mode 100644 index 00000000..c79e4e05 --- /dev/null +++ b/scripts/golang/services/config_reader_service_test.go @@ -0,0 +1,26 @@ +package services_test + +import ( + "github.com/cisco-lockhart/cloud-fw-mgr-api-docs/services" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("ConfigReaderService", func() { + Describe("LoadConfig", func() { + It("should return a valid configuration", func() { + url := "https://raw.githubusercontent.com/cisco-lockhart/cdo-public-api-docs/refs/heads/LH-89186-improve-api-doc-generation/cloud-fw-mgr-api-docs.config.yaml" + config, err := services.LoadConfig(url) + Expect(config).NotTo(BeNil()) + Expect(err).To(BeNil()) + Expect(config.Services).NotTo(BeEmpty()) + }) + + It("should fail if the URL is not a valid YAML", func() { + invalidUrl := "https://raw.githubusercontent.com/cisco-lockhart/cdo-public-api-docs/refs/heads/main/README.md" + config, err := services.LoadConfig(invalidUrl) + Expect(config).To(BeNil()) + Expect(err).NotTo(BeNil()) + }) + }) +}) diff --git a/scripts/golang/services/installer_service.go b/scripts/golang/services/installer_service.go new file mode 100644 index 00000000..8f07aa53 --- /dev/null +++ b/scripts/golang/services/installer_service.go @@ -0,0 +1,25 @@ +package services + +func InstallNodePackage(packagename string) error { + return InstallNodePackageGloballyOrLocally(packagename, true) +} + +func InstallNodePackageGloballyOrLocally(packagename string, global bool) error { + var err error + if global { + _, err = ExecCommand("npm", "install", "-g", packagename).Output() + } else { + _, err = ExecCommand("npm", "install", packagename).Output() + } + return err +} + +func InstallHomebrewPackage(packagename string) error { + _, err := ExecCommand("brew", "install", packagename).Output() + return err +} + +func InstallPipxPackage(packagename string) error { + _, err := ExecCommand("pipx", "install", packagename, "--include-deps").Output() + return err +} diff --git a/scripts/golang/services/installer_service_test.go b/scripts/golang/services/installer_service_test.go new file mode 100644 index 00000000..04f57e36 --- /dev/null +++ b/scripts/golang/services/installer_service_test.go @@ -0,0 +1,156 @@ +package services_test + +import ( + "errors" + "github.com/cisco-lockhart/cloud-fw-mgr-api-docs/services" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +type MockCommandExecutor struct { + // Used to stub the return of the Output method + // Could add other properties depending on testing needs + output *string + err error +} + +// Implements the commandExecutor interface +func (m *MockCommandExecutor) Output() ([]byte, error) { + if m.output == nil { + return nil, m.err + } else { + return []byte(*m.output), m.err + } +} + +var _ = Describe("InstallerService", func() { + Context("InstallNodePackage", func() { + var realExecCommand func(name string, args ...string) services.CommandExecutor + BeforeEach(func() { + realExecCommand = services.ExecCommand + }) + + AfterEach(func() { + services.ExecCommand = realExecCommand + }) + + It("should install a node package globally", func() { + command := "test-pkg" + mockShellCommandFunc := func(name string, args ...string) services.CommandExecutor { + // validate args + Expect(name).To(Equal("npm")) + Expect(args).To(HaveLen(3)) + Expect(args).To(Equal([]string{"install", "-g", command})) + + output := "test output" + return &MockCommandExecutor{output: &output} + } + services.ExecCommand = mockShellCommandFunc + err := services.InstallNodePackage(command) + Expect(err).To(BeNil()) + }) + + It("should fail if node package installation fails", func() { + command := "test-pkg" + expectedErrMsg := "test-error" + mockShellCommandFunc := func(name string, args ...string) services.CommandExecutor { + // validate args + Expect(name).To(Equal("npm")) + Expect(args).To(HaveLen(3)) + Expect(args).To(Equal([]string{"install", "-g", command})) + + return &MockCommandExecutor{output: nil, err: errors.New(expectedErrMsg)} + } + services.ExecCommand = mockShellCommandFunc + err := services.InstallNodePackage(command) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(Equal(expectedErrMsg)) + }) + + It("should install a node package locally", func() { + command := "test-pkg" + mockShellCommandFunc := func(name string, args ...string) services.CommandExecutor { + // validate args + Expect(name).To(Equal("npm")) + Expect(args).To(HaveLen(2)) + Expect(args).To(Equal([]string{"install", command})) + + output := "test output" + return &MockCommandExecutor{output: &output} + } + services.ExecCommand = mockShellCommandFunc + err := services.InstallNodePackageGloballyOrLocally(command, false) + Expect(err).To(BeNil()) + }) + }) + + Context("InstallHomebrewPackage", func() { + It("should install a homebrew package", func() { + command := "test-pkg" + mockShellCommandFunc := func(name string, args ...string) services.CommandExecutor { + // validate args + Expect(name).To(Equal("brew")) + Expect(args).To(HaveLen(2)) + Expect(args).To(Equal([]string{"install", command})) + + output := "test output" + return &MockCommandExecutor{output: &output} + } + services.ExecCommand = mockShellCommandFunc + err := services.InstallHomebrewPackage(command) + Expect(err).To(BeNil()) + }) + + It("should fail to install a Homebrew package", func() { + command := "test-pkg" + expectedErrMsg := "test-error" + mockShellCommandFunc := func(name string, args ...string) services.CommandExecutor { + // validate args + Expect(name).To(Equal("brew")) + Expect(args).To(HaveLen(2)) + Expect(args).To(Equal([]string{"install", command})) + + return &MockCommandExecutor{output: nil, err: errors.New(expectedErrMsg)} + } + services.ExecCommand = mockShellCommandFunc + err := services.InstallHomebrewPackage(command) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(Equal(expectedErrMsg)) + }) + }) + + Context("InstallPipxPackage", func() { + It("should install Pipx package", func() { + command := "test-pkg" + mockShellCommandFunc := func(name string, args ...string) services.CommandExecutor { + // validate args + Expect(name).To(Equal("pipx")) + Expect(args).To(HaveLen(3)) + Expect(args).To(Equal([]string{"install", command, "--include-deps"})) + + output := "test output" + return &MockCommandExecutor{output: &output} + } + services.ExecCommand = mockShellCommandFunc + err := services.InstallPipxPackage(command) + Expect(err).To(BeNil()) + }) + + It("should fail to install a Pipx package", func() { + command := "test-pkg" + expectedErrMsg := "test-error" + mockShellCommandFunc := func(name string, args ...string) services.CommandExecutor { + // validate args + Expect(name).To(Equal("pipx")) + Expect(args).To(HaveLen(3)) + Expect(args).To(Equal([]string{"install", command, "--include-deps"})) + + return &MockCommandExecutor{output: nil, err: errors.New(expectedErrMsg)} + } + services.ExecCommand = mockShellCommandFunc + err := services.InstallPipxPackage(command) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(Equal(expectedErrMsg)) + }) + }) +}) diff --git a/scripts/golang/services/openapi_merger_service.go b/scripts/golang/services/openapi_merger_service.go new file mode 100644 index 00000000..c4a116ea --- /dev/null +++ b/scripts/golang/services/openapi_merger_service.go @@ -0,0 +1,46 @@ +package services + +import ( + "errors" + "fmt" + "github.com/cisco-lockhart/cloud-fw-mgr-api-docs/models" + "github.com/pterm/pterm" +) + +func MergeOpenApiSpecs(serviceSpecs map[string]*models.OpenAPI, config *models.Config) (*models.OpenAPI, error) { + mergedSpec := models.OpenAPI{} + mergedSpec.OpenAPI = "3.0.1" + mergedSpec.Info = config.Info + mergedSpec.Servers = config.Servers + mergedSpec.Paths = make(map[string]interface{}) + mergedSpec.Components = models.Components{} + mergedSpec.Components.Schemas = make(map[string]interface{}) + mergedSpec.Components.Responses = make(map[string]interface{}) + mergedSpec.Components.SecuritySchemes = config.SecuritySchemes + + for serviceName, spec := range serviceSpecs { + spinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Merging OpenAPI spec for service: %s...", serviceName)) + // Merge paths + for path, pathItem := range spec.Paths { + if _, exists := mergedSpec.Paths[path]; exists { + return nil, errors.New(fmt.Sprintf("duplicate path found: %s", path)) + } + mergedSpec.Paths[path] = pathItem + } + + // Merge components + // Merge schemas + for schemaName, schema := range spec.Components.Schemas { + mergedSpec.Components.Schemas[schemaName] = schema + } + + // Merge responses + for responseName, response := range spec.Components.Responses { + mergedSpec.Components.Responses[responseName] = response + } + + spinner.Success(fmt.Sprintf("Merged OpenAPI spec for service: %s", serviceName)) + } + + return &mergedSpec, nil +} diff --git a/scripts/golang/services/openapi_merger_service_test.go b/scripts/golang/services/openapi_merger_service_test.go new file mode 100644 index 00000000..cbb994ca --- /dev/null +++ b/scripts/golang/services/openapi_merger_service_test.go @@ -0,0 +1,125 @@ +package services_test + +import ( + "github.com/cisco-lockhart/cloud-fw-mgr-api-docs/models" + "github.com/cisco-lockhart/cloud-fw-mgr-api-docs/services" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("MergeOpenApiSpecs", func() { + It("should merge multiple OpenAPI specs", func() { + config := models.Config{ + Info: models.Info{ + Title: "API Docs", + Version: "1.0.0", + Description: "Description", + Contact: models.Contact{ + Name: "SCC TAC", + Email: "cdo.tac@cisco.com", + }, + }, + Servers: []models.Server{models.Server{ + URL: "https://edge.us.cdo.cisco.com", + Description: "US", + }}, + } + + service1Paths := map[string]interface{}{ + "/path1": map[string]interface{}{}, + "/path2": map[string]interface{}{}, + } + service1Schemas := map[string]interface{}{ + "schema1": map[string]interface{}{}, + "schema2": map[string]interface{}{}, + } + service1Responses := map[string]interface{}{ + "response1": map[string]interface{}{}, + "response2": map[string]interface{}{}, + } + service2Paths := map[string]interface{}{ + "/path3": map[string]interface{}{}, + "/path4": map[string]interface{}{}, + } + service2Schemas := map[string]interface{}{ + "schema3": map[string]interface{}{}, + "schema2": map[string]interface{}{}, + } + service2Responses := map[string]interface{}{ + "response3": map[string]interface{}{}, + "response2": map[string]interface{}{}, + } + + serviceSpecs := map[string]*models.OpenAPI{ + "service1": { + OpenAPI: "", + Paths: service1Paths, + Components: models.Components{ + Schemas: service1Schemas, + Responses: service1Responses, + }, + Definitions: nil, + }, + "service2": { + OpenAPI: "", + Paths: service2Paths, + Components: models.Components{ + Schemas: service2Schemas, + Responses: service2Responses, + }, + Definitions: nil, + }, + } + + mergedSpec, err := services.MergeOpenApiSpecs(serviceSpecs, &config) + Expect(err).To(BeNil()) + Expect(mergedSpec).NotTo(BeNil()) + Expect(mergedSpec.Paths).To(HaveLen(4)) + Expect(mergedSpec.Components.Schemas).To(HaveLen(3)) + Expect(mergedSpec.Components.Responses).To(HaveLen(3)) + }) + + It("Should fail on conflicting paths", func() { + config := models.Config{ + Info: models.Info{ + Title: "API Docs", + Version: "1.0.0", + Description: "Description", + Contact: models.Contact{ + Name: "SCC TAC", + Email: "cdo.tac@cisco.com", + }, + }, + Servers: []models.Server{models.Server{ + URL: "https://edge.us.cdo.cisco.com", + Description: "US", + }}, + } + + service1Paths := map[string]interface{}{ + "/path1": map[string]interface{}{}, + "/path2": map[string]interface{}{}, + } + service2Paths := map[string]interface{}{ + "/path1": map[string]interface{}{}, + "/path4": map[string]interface{}{}, + } + + serviceSpecs := map[string]*models.OpenAPI{ + "service1": { + OpenAPI: "", + Paths: service1Paths, + Definitions: nil, + }, + "service2": { + OpenAPI: "", + Paths: service2Paths, + Definitions: nil, + }, + } + + mergedSpec, err := services.MergeOpenApiSpecs(serviceSpecs, &config) + Expect(err).NotTo(BeNil()) + Expect(mergedSpec).To(BeNil()) + }) +}) diff --git a/scripts/golang/services/openapi_reader_service.go b/scripts/golang/services/openapi_reader_service.go new file mode 100644 index 00000000..00302464 --- /dev/null +++ b/scripts/golang/services/openapi_reader_service.go @@ -0,0 +1,37 @@ +package services + +import ( + "github.com/cisco-lockhart/cloud-fw-mgr-api-docs/models" + "gopkg.in/yaml.v3" + "io" + "net/http" +) + +func LoadOpenApi(url string) (*models.OpenAPI, error) { + // Make an HTTP GET request + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + panic(err) + } + }(resp.Body) + + // Read the response body + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + // unmarshall it + openApiSpec := models.OpenAPI{} + err = yaml.Unmarshal(data, &openApiSpec) + if err != nil { + return nil, err + } + + return &openApiSpec, nil +} diff --git a/scripts/golang/services/openapi_reader_service_test.go b/scripts/golang/services/openapi_reader_service_test.go new file mode 100644 index 00000000..0ed2e004 --- /dev/null +++ b/scripts/golang/services/openapi_reader_service_test.go @@ -0,0 +1,30 @@ +package services_test + +import ( + "github.com/cisco-lockhart/cloud-fw-mgr-api-docs/services" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("LoadOpenApi", func() { + It("Should successfully load an OpenAPI spec", func() { + openApi, err := services.LoadOpenApi("https://edge.us.cdo.cisco.com/api/platform/public-api/v3/api-docs.yaml") + Expect(openApi).NotTo(BeNil()) + Expect(err).To(BeNil()) + }) + + It("Should fail to load an OpenAPI spec if the URL does not exist", func() { + // this URL does not exist + var url = "https://cisco.doesnotexist.com/v3/openapi.yaml" + openApi, err := services.LoadOpenApi(url) + Expect(openApi).To(BeNil()) + Expect(err).NotTo(BeNil()) + }) + + It("Should fail to load an OpenAPI spec if the URL is not an OpenAPI YAML", func() { + var url = "https://www.cisco.com" + openApi, err := services.LoadOpenApi(url) + Expect(openApi).To(BeNil()) + Expect(err).NotTo(BeNil()) + }) +}) diff --git a/scripts/golang/services/postman_service.go b/scripts/golang/services/postman_service.go new file mode 100644 index 00000000..e91e9f0a --- /dev/null +++ b/scripts/golang/services/postman_service.go @@ -0,0 +1,9 @@ +package services + +func GeneratePostmanCollection(openapiFile string, postmanCollectionFile string) error { + _, err := ExecCommand("npx", "openapi2postmanv2", "-s", openapiFile, "-o", postmanCollectionFile, "-O", "folderStrategy=Tags").Output() + if err != nil { + return err + } + return nil +} diff --git a/scripts/golang/services/postman_service_test.go b/scripts/golang/services/postman_service_test.go new file mode 100644 index 00000000..7dabf69e --- /dev/null +++ b/scripts/golang/services/postman_service_test.go @@ -0,0 +1,51 @@ +package services_test + +import ( + "errors" + "github.com/cisco-lockhart/cloud-fw-mgr-api-docs/services" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("GeneratePostmanCollection", func() { + var realExecCommand func(name string, args ...string) services.CommandExecutor + BeforeEach(func() { + realExecCommand = services.ExecCommand + }) + + AfterEach(func() { + services.ExecCommand = realExecCommand + }) + + It("should call a command to generate a Postman collection", func() { + openApiFile := "openapi.yaml" + postmanCollection := "postman.json" + mockShellCommandFunc := func(name string, args ...string) services.CommandExecutor { + // validate args + Expect(name).To(Equal("npx")) + Expect(args).To(HaveLen(7)) + Expect(args).To(Equal([]string{"openapi2postmanv2", "-s", openApiFile, "-o", postmanCollection, "-O", "folderStrategy=Tags"})) + + output := "test output" + return &MockCommandExecutor{output: &output} + } + services.ExecCommand = mockShellCommandFunc + err := services.GeneratePostmanCollection(openApiFile, postmanCollection) + Expect(err).To(BeNil()) + }) + + It("should error out if the command errored out", func() { + openApiFile := "openapi.yaml" + postmanCollection := "postman.json" + expectedErrMsg := "test-error" + mockShellCommandFunc := func(name string, args ...string) services.CommandExecutor { + // validate args + return &MockCommandExecutor{output: nil, err: errors.New(expectedErrMsg)} + } + + services.ExecCommand = mockShellCommandFunc + err := services.GeneratePostmanCollection(openApiFile, postmanCollection) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(Equal(expectedErrMsg)) + }) +}) diff --git a/scripts/golang/services/pypi_service.go b/scripts/golang/services/pypi_service.go new file mode 100644 index 00000000..c662561c --- /dev/null +++ b/scripts/golang/services/pypi_service.go @@ -0,0 +1,57 @@ +package services + +import ( + "encoding/json" + "errors" + "github.com/Masterminds/semver/v3" + "github.com/cisco-lockhart/cloud-fw-mgr-api-docs/models" + "io" + "net/http" +) + +const pypiPackageName = "cdo_sdk_python" + +func GetCurrentVersion(openApiVersionStr string) (*string, error) { + resp, err := http.Get("https://pypi.org/pypi/" + pypiPackageName + "/json") + if err != nil { + return nil, err + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + panic(err) + } + }(resp.Body) + // Check if the request was successful + if resp.StatusCode != http.StatusOK { + return nil, errors.New("failed to fetch URL: " + resp.Request.URL.String() + ", status code: " + resp.Status) + } + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + pypiVersionInfo := models.PypiVersionInfo{} + if err = json.Unmarshal(data, &pypiVersionInfo); err != nil { + return nil, err + } + + if pypiVersionInfo.Info.Version == "" { + return &openApiVersionStr, nil + } + + openApiVersion, err := semver.NewVersion(openApiVersionStr) + if err != nil { + return nil, err + } + pypiVersion, err := semver.NewVersion(pypiVersionInfo.Info.Version) + if err != nil { + return nil, err + } + if openApiVersion.Major() == pypiVersion.Major() && openApiVersion.Minor() == pypiVersion.Minor() { + newVersion := pypiVersion.IncPatch() + newVersionStr := newVersion.String() + return &newVersionStr, nil + } + + return &openApiVersionStr, nil +} diff --git a/scripts/golang/services/pypi_service_test.go b/scripts/golang/services/pypi_service_test.go new file mode 100644 index 00000000..88e7da07 --- /dev/null +++ b/scripts/golang/services/pypi_service_test.go @@ -0,0 +1,15 @@ +package services_test + +import ( + "github.com/cisco-lockhart/cloud-fw-mgr-api-docs/services" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("GetCurrentVersion", func() { + It("should return the current version", func() { + version, err := services.GetCurrentVersion("0.0.1") + Expect(version).NotTo(BeNil()) + Expect(err).To(BeNil()) + }) +}) diff --git a/scripts/golang/services/sdks_service.go b/scripts/golang/services/sdks_service.go new file mode 100644 index 00000000..848296fb --- /dev/null +++ b/scripts/golang/services/sdks_service.go @@ -0,0 +1,62 @@ +package services + +import ( + "context" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/secretsmanager" + "github.com/pterm/pterm" +) + +func GeneratePythonSdk(openapiFile string, version string, useLocalInstallation bool) error { + var commandName string + if useLocalInstallation { + err := InstallNodePackageGloballyOrLocally("@openapitools/openapi-generator-cli", false) + if err != nil { + return err + } + commandName = "./node_modules/@openapitools/openapi-generator-cli" + } else { + commandName = "@openapitools/openapi-generator-cli" + } + _, err := ExecCommand("npx", + commandName, + "generate", + "-i", openapiFile, + "-g", "python", + "-o", "sdks/python", + "--additional-properties=packageName=cdo-sdk-python,packageVersion="+version).Output() + return err +} + +func PublishPythonSdk(pypiToken *string, version string) error { + if pypiToken == nil { + var err error + pypiToken, err = getPyPiTokenFromSecretsManager() + if err != nil { + return err + } + } + cmd := ExecCommand("bash", "-c", "cd sdks/python && rm -rf dist build *.egg-info && python3 setup.py sdist bdist_wheel") + out, err := cmd.Output() + if err != nil { + pterm.Error.Println(string(out)) + } + return err +} + +func getPyPiTokenFromSecretsManager() (*string, error) { + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + return nil, err + } + secretsManagerClient := secretsmanager.NewFromConfig(cfg) + secretId := "arn:aws:secretsmanager:us-west-2:107042026245:secret:jenkins-pypi-credentials-mD4NdK" + secretValue, err := secretsManagerClient.GetSecretValue(context.TODO(), &secretsmanager.GetSecretValueInput{ + SecretId: &secretId, + }) + if err != nil { + return nil, err + } + + return secretValue.SecretString, nil +} diff --git a/scripts/golang/services/services_suite_test.go b/scripts/golang/services/services_suite_test.go new file mode 100644 index 00000000..fad35fdf --- /dev/null +++ b/scripts/golang/services/services_suite_test.go @@ -0,0 +1,13 @@ +package services_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestServices(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Services Suite") +}