diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e4ca88f..df91732 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,18 +1,13 @@ // For format details, see https://aka.ms/devcontainer.json. { "name": "Codefresh Support Package", - "image": "mcr.microsoft.com/devcontainers/base:ubuntu", - "onCreateCommand": "curl -fsSL https://deno.land/install.sh | sh -s -- -y", + "image": "mcr.microsoft.com/devcontainers/go:1", "customizations": { "vscode": { - "settings": { - "deno.enable": true, - "deno.lint": true - }, "extensions": [ - "denoland.vscode-deno", "davidanson.vscode-markdownlint", - "redhat.vscode-yaml" + "redhat.vscode-yaml", + "golang.go" ] } } diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index c2903ec..ec2fe47 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -3,47 +3,28 @@ name: Release on: push: tags: - - 'v*' # Triggers the workflow on new tags that start with 'v' + - 'v*' # Trigger only on tags starting with "v" (e.g., v1.0.0) jobs: - build: + release: runs-on: ubuntu-latest + permissions: + contents: write # Needed to create GitHub releases + steps: - - name: Checkout repository - uses: actions/checkout@v2 + - name: Checkout code + uses: actions/checkout@v4 - - name: Set up Deno - uses: denoland/setup-deno@v2 + - name: Set up Go + uses: actions/setup-go@v5 with: - deno-version: vx.x.x - - - name: Compile binaries - run: | - sed -i "s/__APP_VERSION__/$TAG_NAME/g" main.js - deno task compile - env: - TAG_NAME: ${{ github.ref_name }} + go-version: '1.24' # Use the version of Go your project requires - - name: Package binaries - run: | - zip ./bin/cf-support_windows_x86_64.zip ./bin/cf-support_windows_x86_64.exe - tar -czvf ./bin/cf-support_darwin_x86_64.tar.gz ./bin/cf-support_darwin_x86_64 - tar -czvf ./bin/cf-support_darwin_arm64.tar.gz ./bin/cf-support_darwin_arm64 - tar -czvf ./bin/cf-support_linux_x86_64.tar.gz ./bin/cf-support_linux_x86_64 - - - name: Create GitHub Release - id: create_release - uses: ncipollo/release-action@v1 + - name: Install GoReleaser + uses: goreleaser/goreleaser-action@v6 with: - artifacts: | - ./bin/cf-support_windows_x86_64.zip - ./bin/cf-support_darwin_x86_64.tar.gz - ./bin/cf-support_darwin_arm64.tar.gz - ./bin/cf-support_linux_x86_64.tar.gz - token: ${{ secrets.GITHUB_TOKEN }} - tag: ${{ github.ref_name }} - name: ${{ github.ref_name }} - body: ${{ github.event.head_commit.message }} - draft: false - prerelease: false + version: latest # Or pin a specific version + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 13c73c3..02d47d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,132 +1,28 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# env file .env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp -.cache - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* +#macos files .DS_Store -bin \ No newline at end of file diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..803d20f --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,28 @@ +project_name: cf-support +builds: + - id: default + main: ./main.go + goos: + - linux + - windows + - darwin + goarch: + - amd64 + - arm64 + ldflags: + - "-s -w -X github.com/codefresh-support/codefresh-support-package/cmd.Version={{.Version}}" + +archives: + - format: tar.gz + name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" + format_overrides: + - goos: windows + format: zip # Use zip format for Windows builds + +checksum: + name_template: "checksums.txt" + +release: + github: + owner: codefresh-support + name: codefresh-support-package diff --git a/LICENSE b/LICENSE index 582c405..69ad89d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ -MIT License +The MIT License (MIT) -Copyright (c) 2024 Codefresh Support +Copyright © 2025 Codefresh Support Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index 8c4e6d8..3070086 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Codefresh Support Package -This project is designed to gather data from Hybrid Runtimes for Codefresh SaaS platform, and Hybrid Runtimes and OnPrem isntallation on the OnPrem Platform. It collects information about various Kubernetes resources such as Pods, Nodes, Configmaps, Services, and Events. For Classic and OnPrem we gather some informtion from the platform itself. +This project is designed to gather data from Codefresh Hybrid Runtimes OnPrem isntallation, and Open Source ArgoCD. It collects information about various Kubernetes resources such as Pods, Nodes, Configmaps, Services, and Events. For Pipelines and OnPrem we gather some informtion from the platform itself. ## Prereqs @@ -11,7 +11,7 @@ This project is designed to gather data from Hybrid Runtimes for Codefresh SaaS - Or the following ENV vars set. - `CF_API_KEY`: Codefresh API Token - `CF_URL`: URL of the platform (ex: `https://g.codefresh.io`) - - Need an Account Admin Token for Claasic Hybrid Runtime. + - Need an Account Admin Token for Pipelines Hybrid Runtime. - Need a System Admin Token for the OnPrem Installation. - JQ - Used only to get the latest version of the binary for *nix systems. @@ -34,14 +34,14 @@ chmod +x cf-support ./cf-support ``` -### macOS - x86_64 +### macOS - amd64 ```shell # get the latest version or change to a specific version VERSION=$(curl --silent "https://api.github.com/repos/codefresh-support/codefresh-support-package/releases/latest" | jq -r ".tag_name") # download and extract the binary -curl -L --output - https://github.com/codefresh-support/codefresh-support-package/releases/download/$VERSION/cf-support_darwin_x86_64.tar.gz | tar zx -O > cf-support +curl -L --output - https://github.com/codefresh-support/codefresh-support-package/releases/download/$VERSION/cf-support_darwin_amd64.tar.gz | tar zx -O > cf-support # set execution to binary chmod +x cf-support @@ -50,14 +50,14 @@ chmod +x cf-support ./cf-support ``` -### linux - x86_64 +### linux - arm64 ```shell # get the latest version or change to a specific version VERSION=$(curl --silent "https://api.github.com/repos/codefresh-support/codefresh-support-package/releases/latest" | jq -r ".tag_name") # download and extract the binary -curl -L --output - https://github.com/codefresh-support/codefresh-support-package/releases/download/$VERSION/cf-support_linux_x86_64.tar.gz | tar zx -O > cf-support +curl -L --output - https://github.com/codefresh-support/codefresh-support-package/releases/download/$VERSION/cf-support_linux_arm64.tar.gz | tar zx -O > cf-support # set execution to binary chmod +x cf-support @@ -66,10 +66,26 @@ chmod +x cf-support ./cf-support ``` -### Windows - x86_64 +### linux - amd64 + +```shell +# get the latest version or change to a specific version +VERSION=$(curl --silent "https://api.github.com/repos/codefresh-support/codefresh-support-package/releases/latest" | jq -r ".tag_name") + +# download and extract the binary +curl -L --output - https://github.com/codefresh-support/codefresh-support-package/releases/download/$VERSION/cf-support_linux_amd64.tar.gz | tar zx -O > cf-support + +# set execution to binary +chmod +x cf-support + +# run application +./cf-support +``` + +### Windows - arm64/amd6 1. Go the the [Latest](https://github.com/codefresh-support/codefresh-support-package/releases/latest) release. -1. Download the cf-support_windows_x86_64.zip file +1. Download the cf-support_windows_arm64.zip / cf-support_windows_amd64.zip file 1. Run the `.exe` file via CMD or PowerShell ## How to Release a New Version diff --git a/cmd/gitops.go b/cmd/gitops.go new file mode 100644 index 0000000..7d93504 --- /dev/null +++ b/cmd/gitops.go @@ -0,0 +1,84 @@ +/* +Copyright © 2025 Codefresh Support + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package cmd + +import ( + "fmt" + "strings" + "time" + + "github.com/codefresh-support/codefresh-support-package/internal/k8s" + "github.com/codefresh-support/codefresh-support-package/internal/utils" + "github.com/spf13/cobra" +) + +var gitOpsNamespace string + +// gitopsCmd represents the gitops command +var gitopsCmd = &cobra.Command{ + Use: "gitops", + Short: "Collect data for the Codefresh GitOps Runtime", + Long: `Collect data for the Codefresh GitOps Runtime`, + Run: func(cmd *cobra.Command, args []string) { + const RuntimeType = "Codefresh GitOps Runtime" + dirPath := fmt.Sprintf("./codefresh-support-%d", time.Now().Unix()) + if gitOpsNamespace == "" { + var err error + gitOpsNamespace, err = k8s.SelectNamespace(RuntimeType) + if err != nil { + cmd.PrintErrln("Error selecting namespace:", err) + return + } + } + cmd.Printf("Gathering data in %s namespace for %s...\n", gitOpsNamespace, RuntimeType) + + K8sResources := append(k8s.K8sGeneral, append(k8s.K8sGitOps, k8s.K8sArgo...)...) + + if err := utils.FetchAndSaveData(gitOpsNamespace, K8sResources, dirPath, Version); err != nil { + cmd.PrintErrln("Error fetching and saving data:", err) + return + } + + cmd.Println("Data Gathered Successfully.") + + if err := utils.PreparePackage(strings.ReplaceAll(strings.ToLower(RuntimeType), " ", "-"), dirPath); err != nil { + cmd.PrintErrln("Error preparing package:", err) + return + } + }, +} + +func init() { + rootCmd.AddCommand(gitopsCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // gitopsCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // gitopsCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + gitopsCmd.Flags().StringVarP(&gitOpsNamespace, "namespace", "n", "", "The namespace where the Runtime is installed") + +} diff --git a/cmd/onprem.go b/cmd/onprem.go new file mode 100644 index 0000000..ed436bf --- /dev/null +++ b/cmd/onprem.go @@ -0,0 +1,137 @@ +/* +Copyright © 2025 Codefresh Support + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package cmd + +import ( + "fmt" + "strings" + "time" + + "github.com/codefresh-support/codefresh-support-package/internal/codefresh" + "github.com/codefresh-support/codefresh-support-package/internal/k8s" + "github.com/codefresh-support/codefresh-support-package/internal/utils" + "github.com/spf13/cobra" +) + +var onpremNamespace string + +// onpremCmd represents the onprem command +var onpremCmd = &cobra.Command{ + Use: "onprem", + Short: "Collect data for the Codefresh OnPrem Installation", + Long: `Collect data for the Codefresh OnPrem Installation`, + Run: func(cmd *cobra.Command, args []string) { + const RuntimeType = "Codefresh OnPrem" + dirPath := fmt.Sprintf("./codefresh-support-%d", time.Now().Unix()) + cfConfig, err := codefresh.GetCodefreshCreds() + if err != nil { + cmd.PrintErrln("Error getting Codefresh credentials:", err) + return + } + if cfConfig.BaseURL == "https://g.codefresh.io/api" { + cmd.PrintErrln("Cannot gather On-Prem data for Codefresh SaaS. If you need to gather data for Codefresh On-Prem, please update your ./cfconfig context (or Envs) to point to an On-Prem instance.") + cmd.PrintErrln("For Codefresh SaaS, use 'pipelines' or 'gitops' commands.") + return + } + + if onpremNamespace == "" { + var err error + onpremNamespace, err = k8s.SelectNamespace(RuntimeType) + if err != nil { + cmd.PrintErrln("Error selecting namespace:", err) + return + } + } + + cmd.Printf("Gathering data in %s namespace for %s...\n", onpremNamespace, RuntimeType) + + K8sResources := append(k8s.K8sGeneral, k8s.K8sClassicOnPrem...) + + if err := utils.FetchAndSaveData(onpremNamespace, K8sResources, dirPath, Version); err != nil { + cmd.PrintErrln("Error fetching and saving data:", err) + return + } + + onpremAccounts, err := codefresh.OnPremAccounts(cfConfig) + if err != nil { + cmd.PrintErrln("Error fetching On-Prem accounts:", err) + return + } + if err := utils.WriteYaml(onpremAccounts, "onprem-accounts", dirPath); err != nil { + cmd.PrintErrln("Error writing On-Prem accounts:", err) + return + } + + onpremRuntimes, err := codefresh.OnPremRuntimes(cfConfig) + if err != nil { + cmd.PrintErrln("Error fetching On-Prem runtimes:", err) + return + } + if err := utils.WriteYaml(onpremRuntimes, "onprem-runtimes", dirPath); err != nil { + cmd.PrintErrln("Error writing On-Prem runtimes:", err) + return + } + + onpremUsers, err := codefresh.OnPremUsers(cfConfig) + if err != nil { + cmd.PrintErrln("Error fetching On-Prem users:", err) + return + } + if err := utils.WriteYaml(onpremUsers, "onprem-users", dirPath); err != nil { + cmd.PrintErrln("Error writing On-Prem users:", err) + return + } + + onpremFeatureFlags, err := codefresh.OnPremFeatureFlags(cfConfig) + if err != nil { + cmd.PrintErrln("Error fetching On-Prem feature flags:", err) + return + } + if err := utils.WriteYaml(onpremFeatureFlags, "onprem-feature-flags", dirPath); err != nil { + cmd.PrintErrln("Error writing On-Prem feature flags:", err) + return + } + + cmd.Println("Data Gathered Successfully.") + + if err := utils.PreparePackage(strings.ReplaceAll(strings.ToLower(RuntimeType), " ", "-"), dirPath); err != nil { + cmd.PrintErrln("Error preparing package:", err) + return + } + + }, +} + +func init() { + rootCmd.AddCommand(onpremCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // onpremCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // onpremCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + onpremCmd.Flags().StringVarP(&onpremNamespace, "namespace", "n", "", "The namespace where Codefresh OnPrem is installed") +} diff --git a/cmd/oss.go b/cmd/oss.go new file mode 100644 index 0000000..7c3dcf0 --- /dev/null +++ b/cmd/oss.go @@ -0,0 +1,83 @@ +/* +Copyright © 2025 Codefresh Support + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package cmd + +import ( + "fmt" + "strings" + "time" + + "github.com/codefresh-support/codefresh-support-package/internal/k8s" + "github.com/codefresh-support/codefresh-support-package/internal/utils" + "github.com/spf13/cobra" +) + +var ossNamespace string + +// ossCmd represents the oss command +var ossCmd = &cobra.Command{ + Use: "oss", + Short: "Collect data for the Open Source ArgoCD", + Long: `Collect data for the Open Source ArgoCD`, + Run: func(cmd *cobra.Command, args []string) { + const RuntimeType = "OSS ArgoCD" + dirPath := fmt.Sprintf("./codefresh-support-%d", time.Now().Unix()) + if ossNamespace == "" { + var err error + ossNamespace, err = k8s.SelectNamespace(RuntimeType) + if err != nil { + cmd.PrintErrln("Error selecting namespace:", err) + return + } + } + cmd.Printf("Gathering data in %s namespace for %s...\n", ossNamespace, RuntimeType) + + K8sResources := append(k8s.K8sGeneral, k8s.K8sArgo...) + + if err := utils.FetchAndSaveData(ossNamespace, K8sResources, dirPath, Version); err != nil { + cmd.PrintErrln("Error fetching and saving data:", err) + return + } + + cmd.Println("Data Gathered Successfully.") + + if err := utils.PreparePackage(strings.ReplaceAll(strings.ToLower(RuntimeType), " ", "-"), dirPath); err != nil { + cmd.PrintErrln("Error preparing package:", err) + return + } + }, +} + +func init() { + rootCmd.AddCommand(ossCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // ossCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // ossCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + ossCmd.Flags().StringVarP(&ossNamespace, "namespace", "n", "", "The namespace where OSS ArgoCD is installed") +} diff --git a/cmd/pipelines.go b/cmd/pipelines.go new file mode 100644 index 0000000..0a74e60 --- /dev/null +++ b/cmd/pipelines.go @@ -0,0 +1,119 @@ +/* +Copyright © 2025 Codefresh Support + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package cmd + +import ( + "fmt" + "strings" + "time" + + "github.com/codefresh-support/codefresh-support-package/internal/codefresh" + "github.com/codefresh-support/codefresh-support-package/internal/k8s" + "github.com/codefresh-support/codefresh-support-package/internal/utils" + "github.com/spf13/cobra" +) + +// pipelinesCmd represents the pipelines command +var pipelinesCmd = &cobra.Command{ + Use: "pipelines", + Short: "Collect data for the Codefresh Pipelines Runtime", + Long: `Collect data for the Codefresh Pipelines Runtime`, + Run: func(cmd *cobra.Command, args []string) { + const RuntimeType = "Codefresh Pipelines Runtime" + dirPath := fmt.Sprintf("./codefresh-support-%d", time.Now().Unix()) + cfConfig, err := codefresh.GetCodefreshCreds() + if err != nil { + cmd.PrintErrln("Error getting Codefresh credentials:", err) + return + } + + runtimes, err := codefresh.AccountRuntimes(cfConfig) + if err != nil { + cmd.PrintErrln("Error getting Codefresh runtimes:", err) + return + } + + var pipelinesNamespace string + var reSpec map[string]interface{} + + if len(runtimes) != 0 { + var selection int + for { + cmd.Println("Please select the runtime to gather data from (Number):") + _, err := fmt.Scanf("%d", &selection) + if err != nil || selection < 1 || selection > len(runtimes) { + cmd.Println("Invalid selection. Please enter a number corresponding to one of the listed runtimes.") + continue + } + break + + } + reSpec = runtimes[selection-1] + pipelinesNamespace = reSpec["runtimeScheduler"].(map[string]interface{})["cluster"].(map[string]interface{})["namespace"].(string) + } else { + cmd.Println("No runtimes found in Codefresh account.") + pipelinesNamespace, err = k8s.SelectNamespace(RuntimeType) + if err != nil { + cmd.PrintErrf("error getting Kubernetes namespace: %v", err) + return + } + } + + cmd.Printf("Gathering data in %s namespace for %s...\n", pipelinesNamespace, RuntimeType) + + K8sResources := append(k8s.K8sGeneral, k8s.K8sClassicOnPrem...) + + if err := utils.FetchAndSaveData(pipelinesNamespace, K8sResources, dirPath, Version); err != nil { + cmd.PrintErrln("Error fetching and saving data:", err) + return + } + + if reSpec != nil { + if err := utils.WriteYaml(reSpec, "pipelines-runtime-spec", dirPath); err != nil { + cmd.PrintErrln("Error writing runtime spec:", err) + return + } + } + + cmd.Println("Data Gathered Successfully.") + + if err := utils.PreparePackage(strings.ReplaceAll(strings.ToLower(RuntimeType), " ", "-"), dirPath); err != nil { + cmd.PrintErrln("Error preparing package:", err) + return + } + + }, +} + +func init() { + rootCmd.AddCommand(pipelinesCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // pipelinesCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // pipelinesCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..a96fd1a --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,59 @@ +/* +Copyright © 2025 Codefresh Support + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "cf-support", + Short: "Tool to gather information for Codefresh Support", + Long: `Tool to gather information for Codefresh Support.`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// 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() { + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.codefresh-support-package.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + // rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..d87998d --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,52 @@ +/* +Copyright © 2025 Codefresh Support + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package cmd + +import ( + "github.com/spf13/cobra" +) + +var Version string + +// versionCmd represents the version command +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version number of the application", + Long: `All software has versions. This is the version of the application.`, + Run: func(cmd *cobra.Command, args []string) { + cmd.Println("Application Version:", Version) + }, +} + +func init() { + rootCmd.AddCommand(versionCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // versionCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // versionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/deno.json b/deno.json deleted file mode 100644 index 0dfc0d0..0000000 --- a/deno.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "fmt": { - "indentWidth": 2, - "lineWidth": 120, - "singleQuote": true, - "semiColons": true, - "exclude": [ - ".devcontainer/**", - "README.md", - "ci/**", - ".github/**" - ] - }, - "tasks": { - "pre-compile": "rm -rf ./bin && mkdir ./bin", - "compile:linux": "deno compile --config=./deno.json --allow-env --allow-read --allow-write --allow-net --unsafely-ignore-certificate-errors --allow-run --output=./bin/cf-support_linux_x86_64 --target=x86_64-unknown-linux-gnu ./main.js", - "compile:windows": "deno compile --config=./deno.json --allow-env --allow-read --allow-write --allow-net --unsafely-ignore-certificate-errors --allow-run --output=./bin/cf-support_windows_x86_64 --target=x86_64-pc-windows-msvc ./main.js", - "compile:apple": "deno compile --config=./deno.json --allow-env --allow-read --allow-write --allow-net --unsafely-ignore-certificate-errors --allow-run --output=./bin/cf-support_darwin_x86_64 --target=x86_64-apple-darwin ./main.js", - "compile:apple_arm64": "deno compile --config=./deno.json --allow-env --allow-read --allow-write --allow-net --unsafely-ignore-certificate-errors --allow-run --output=./bin/cf-support_darwin_arm64 --target=aarch64-apple-darwin ./main.js", - "compile": "deno task pre-compile && deno task compile:linux && deno task compile:windows && deno task compile:apple && deno task compile:apple_arm64" - }, - "imports": { - "@deno-library/compress": "jsr:@deno-library/compress@^0.5.5", - "@henrygd/semaphore": "jsr:@henrygd/semaphore@^0.0.2", - "@std/yaml": "jsr:@std/yaml@^1.0.5" - } -} diff --git a/deno.lock b/deno.lock deleted file mode 100644 index dd2078e..0000000 --- a/deno.lock +++ /dev/null @@ -1,77 +0,0 @@ -{ - "version": "4", - "specifiers": { - "jsr:@deno-library/compress@~0.5.5": "0.5.5", - "jsr:@deno-library/crc32@1.0.2": "1.0.2", - "jsr:@henrygd/semaphore@^0.0.2": "0.0.2", - "jsr:@std/bytes@^1.0.2": "1.0.4", - "jsr:@std/fs@1.0.5": "1.0.5", - "jsr:@std/io@0.225.0": "0.225.0", - "jsr:@std/path@1.0.8": "1.0.8", - "jsr:@std/path@^1.0.7": "1.0.8", - "jsr:@std/streams@^1.0.7": "1.0.8", - "jsr:@std/tar@0.1.3": "0.1.3", - "jsr:@std/yaml@1.0.5": "1.0.5", - "jsr:@std/yaml@^1.0.5": "1.0.5", - "jsr:@zip-js/zip-js@2.7.53": "2.7.53" - }, - "jsr": { - "@deno-library/compress@0.5.5": { - "integrity": "18b651a33eac87d96ae8c941487045724a665d654e9d94120da43777393655d9", - "dependencies": [ - "jsr:@deno-library/crc32", - "jsr:@std/fs", - "jsr:@std/io", - "jsr:@std/path@1.0.8", - "jsr:@std/tar", - "jsr:@zip-js/zip-js" - ] - }, - "@deno-library/crc32@1.0.2": { - "integrity": "d2061bfee30c87c97f285dfca0fdc4458e632dc072a33ecfc73ca5177a5a39a0" - }, - "@henrygd/semaphore@0.0.2": { - "integrity": "cdf678250474b9445648d4fb8d8c636fe313f54893a50de616bf8d3e14f3b51e" - }, - "@std/bytes@1.0.4": { - "integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc" - }, - "@std/fs@1.0.5": { - "integrity": "41806ad6823d0b5f275f9849a2640d87e4ef67c51ee1b8fb02426f55e02fd44e", - "dependencies": [ - "jsr:@std/path@^1.0.7" - ] - }, - "@std/io@0.225.0": { - "integrity": "c1db7c5e5a231629b32d64b9a53139445b2ca640d828c26bf23e1c55f8c079b3", - "dependencies": [ - "jsr:@std/bytes" - ] - }, - "@std/path@1.0.8": { - "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" - }, - "@std/streams@1.0.8": { - "integrity": "b41332d93d2cf6a82fe4ac2153b930adf1a859392931e2a19d9fabfb6f154fb3" - }, - "@std/tar@0.1.3": { - "integrity": "531270fc707b37ab9b5f051aa4943e7b16b86905e0398a4ebe062983b0c93115", - "dependencies": [ - "jsr:@std/streams" - ] - }, - "@std/yaml@1.0.5": { - "integrity": "71ba3d334305ee2149391931508b2c293a8490f94a337eef3a09cade1a2a2742" - }, - "@zip-js/zip-js@2.7.53": { - "integrity": "acea5bd8e01feb3fe4c242cfbde7d33dd5e006549a4eb1d15283bc0c778ed672" - } - }, - "workspace": { - "dependencies": [ - "jsr:@deno-library/compress@~0.5.5", - "jsr:@henrygd/semaphore@^0.0.2", - "jsr:@std/yaml@^1.0.5" - ] - } -} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9ef331a --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.com/codefresh-support/codefresh-support-package + +go 1.24.0 + +require ( + github.com/spf13/cobra v1.9.1 + gopkg.in/yaml.v2 v2.4.0 +) + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.6 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d4a36ba --- /dev/null +++ b/go.sum @@ -0,0 +1,13 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/codefresh/account_runtimes.go b/internal/codefresh/account_runtimes.go new file mode 100644 index 0000000..ed015ef --- /dev/null +++ b/internal/codefresh/account_runtimes.go @@ -0,0 +1,35 @@ +package codefresh + +import ( + "encoding/json" + "fmt" + "net/http" +) + +func AccountRuntimes(cfConfig *CodefreshConfig) ([]map[string]interface{}, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/runtime-environments", cfConfig.BaseURL), nil) + if err != nil { + return nil, err + } + for key, value := range cfConfig.Headers { + req.Header.Set(key, value) + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to get runtime environments: %s", resp.Status) + } + + var runtimes []map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&runtimes); err != nil { + return nil, err + } + + return runtimes, nil +} diff --git a/internal/codefresh/creds.go b/internal/codefresh/creds.go new file mode 100644 index 0000000..8d0b5a7 --- /dev/null +++ b/internal/codefresh/creds.go @@ -0,0 +1,65 @@ +package codefresh + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + + "gopkg.in/yaml.v2" +) + +type CodefreshConfig struct { + Headers map[string]string + BaseURL string +} + +type Context struct { + Token string `yaml:"token"` + URL string `yaml:"url"` +} + +type Config struct { + Contexts map[string]Context `yaml:"contexts"` + CurrentContext string `yaml:"current-context"` +} + +func GetCodefreshCreds() (*CodefreshConfig, error) { + envToken := os.Getenv("CF_API_KEY") + envUrl := os.Getenv("CF_BASE_URL") + + if envToken != "" && envUrl != "" { + return &CodefreshConfig{ + Headers: map[string]string{"Authorization": envToken}, + BaseURL: fmt.Sprintf("%s/api", envUrl), + }, nil + } + + var configPath string + if runtime.GOOS == "windows" { + configPath = filepath.Join(os.Getenv("USERPROFILE"), ".cfconfig") + } else { + configPath = filepath.Join(os.Getenv("HOME"), ".cfconfig") + } + + configFileContent, err := os.ReadFile(configPath) + if err != nil { + return nil, err + } + + var config Config + if err := yaml.Unmarshal(configFileContent, &config); err != nil { + return nil, err + } + + currentContext, exists := config.Contexts[config.CurrentContext] + if !exists { + return nil, errors.New("current context not found in Codefresh config") + } + + return &CodefreshConfig{ + Headers: map[string]string{"Authorization": currentContext.Token}, + BaseURL: fmt.Sprintf("%s/api", currentContext.URL), + }, nil +} diff --git a/internal/codefresh/onprem_accounts.go b/internal/codefresh/onprem_accounts.go new file mode 100644 index 0000000..e36c60f --- /dev/null +++ b/internal/codefresh/onprem_accounts.go @@ -0,0 +1,35 @@ +package codefresh + +import ( + "encoding/json" + "fmt" + "net/http" +) + +func OnPremAccounts(cfConfig *CodefreshConfig) (interface{}, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/admin/accounts", cfConfig.BaseURL), nil) + if err != nil { + return nil, err + } + for key, value := range cfConfig.Headers { + req.Header.Set(key, value) + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to get accounts: %s", resp.Status) + } + + var accounts interface{} + if err := json.NewDecoder(resp.Body).Decode(&accounts); err != nil { + return nil, err + } + + return accounts, nil +} diff --git a/internal/codefresh/onprem_feature_flags.go b/internal/codefresh/onprem_feature_flags.go new file mode 100644 index 0000000..6f429c7 --- /dev/null +++ b/internal/codefresh/onprem_feature_flags.go @@ -0,0 +1,35 @@ +package codefresh + +import ( + "encoding/json" + "fmt" + "net/http" +) + +func OnPremFeatureFlags(cfConfig *CodefreshConfig) (interface{}, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/admin/features", cfConfig.BaseURL), nil) + if err != nil { + return nil, err + } + for key, value := range cfConfig.Headers { + req.Header.Set(key, value) + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to get feature flags: %s", resp.Status) + } + + var featureFlags interface{} + if err := json.NewDecoder(resp.Body).Decode(&featureFlags); err != nil { + return nil, err + } + + return featureFlags, nil +} diff --git a/internal/codefresh/onprem_runtimes.go b/internal/codefresh/onprem_runtimes.go new file mode 100644 index 0000000..b6bd2c6 --- /dev/null +++ b/internal/codefresh/onprem_runtimes.go @@ -0,0 +1,35 @@ +package codefresh + +import ( + "encoding/json" + "fmt" + "net/http" +) + +func OnPremRuntimes(cfConfig *CodefreshConfig) (interface{}, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/admin/runtime-environments", cfConfig.BaseURL), nil) + if err != nil { + return nil, err + } + for key, value := range cfConfig.Headers { + req.Header.Set(key, value) + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to get runtimes: %s", resp.Status) + } + + var runtimes interface{} + if err := json.NewDecoder(resp.Body).Decode(&runtimes); err != nil { + return nil, err + } + + return runtimes, nil +} diff --git a/internal/codefresh/onprem_users.go b/internal/codefresh/onprem_users.go new file mode 100644 index 0000000..74e782e --- /dev/null +++ b/internal/codefresh/onprem_users.go @@ -0,0 +1,35 @@ +package codefresh + +import ( + "encoding/json" + "fmt" + "net/http" +) + +func OnPremUsers(cfConfig *CodefreshConfig) (interface{}, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/admin/user?limit=1&page=1", cfConfig.BaseURL), nil) + if err != nil { + return nil, err + } + for key, value := range cfConfig.Headers { + req.Header.Set(key, value) + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to get users: %s", resp.Status) + } + + var users interface{} + if err := json.NewDecoder(resp.Body).Decode(&users); err != nil { + return nil, err + } + + return users, nil +} diff --git a/internal/k8s/describe.go b/internal/k8s/describe.go new file mode 100644 index 0000000..fd1eada --- /dev/null +++ b/internal/k8s/describe.go @@ -0,0 +1,21 @@ +package k8s + +import ( + "bytes" + "fmt" + "os/exec" + "strings" +) + +func Describe(k8sType, namespace, resourceName string) (string, error) { + cmd := exec.Command("kubectl", "describe", strings.ToLower(k8sType), "-n", namespace, resourceName) + var out bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + return "", fmt.Errorf("error describing %s resource: %v: %s", k8sType, err, stderr.String()) + } + return out.String(), nil +} diff --git a/internal/k8s/events.go b/internal/k8s/events.go new file mode 100644 index 0000000..adf4d6e --- /dev/null +++ b/internal/k8s/events.go @@ -0,0 +1,20 @@ +package k8s + +import ( + "bytes" + "fmt" + "os/exec" +) + +func Events(namespace string) (string, error) { + cmd := exec.Command("kubectl", "get", "events", "-n", namespace, "--sort-by=.metadata.creationTimestamp") + var out bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + return "", fmt.Errorf("error getting k8s events: %v: %s", err, stderr.String()) + } + return out.String(), nil +} diff --git a/internal/k8s/get.go b/internal/k8s/get.go new file mode 100644 index 0000000..26e29db --- /dev/null +++ b/internal/k8s/get.go @@ -0,0 +1,46 @@ +package k8s + +import ( + "bytes" + "encoding/json" + "fmt" + "os/exec" + "strings" +) + +type K8sResources struct { + List string + JSON map[string]interface{} +} + +func Get(k8sType, namespace, labelSelector string) (*K8sResources, error) { + cmdList := exec.Command("kubectl", "get", strings.ToLower(k8sType), "-n", namespace, "-l", labelSelector) + var outList bytes.Buffer + var stderrList bytes.Buffer + cmdList.Stdout = &outList + cmdList.Stderr = &stderrList + err := cmdList.Run() + if err != nil { + return nil, fmt.Errorf("error getting %s resources: %v: %s", k8sType, err, stderrList.String()) + } + + cmdJSON := exec.Command("kubectl", "get", strings.ToLower(k8sType), "-n", namespace, "-l", labelSelector, "-o", "json") + var outJSON bytes.Buffer + var stderrJSON bytes.Buffer + cmdJSON.Stdout = &outJSON + cmdJSON.Stderr = &stderrJSON + err = cmdJSON.Run() + if err != nil { + return nil, fmt.Errorf("error getting %s resources: %v: %s", k8sType, err, stderrJSON.String()) + } + + var resourceJSON map[string]interface{} + if err := json.Unmarshal(outJSON.Bytes(), &resourceJSON); err != nil { + return nil, fmt.Errorf("error parsing JSON: %v", err) + } + + return &K8sResources{ + List: outList.String(), + JSON: resourceJSON, + }, nil +} diff --git a/internal/k8s/logs.go b/internal/k8s/logs.go new file mode 100644 index 0000000..e9930f5 --- /dev/null +++ b/internal/k8s/logs.go @@ -0,0 +1,20 @@ +package k8s + +import ( + "bytes" + "fmt" + "os/exec" +) + +func Logs(namespace, podName, containerName string) (string, error) { + cmd := exec.Command("kubectl", "logs", "-n", namespace, podName, "-c", containerName) + var out bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + return "", fmt.Errorf("error geting logs for %s - %s: %v: %s", podName, containerName, err, stderr.String()) + } + return out.String(), nil +} diff --git a/internal/k8s/namespaces.go b/internal/k8s/namespaces.go new file mode 100644 index 0000000..4c579c9 --- /dev/null +++ b/internal/k8s/namespaces.go @@ -0,0 +1,38 @@ +package k8s + +import ( + "bytes" + "fmt" + "os/exec" + "strings" +) + +func SelectNamespace(runtimeType string) (string, error) { + cmd := exec.Command("kubectl", "get", "namespaces", "-o", "jsonpath={.items[*].metadata.name}") + var out bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + return "", fmt.Errorf("unable to get namespaces: %v: %s", err, stderr.String()) + } + + namespaceList := strings.Split(out.String(), " ") + for index, namespace := range namespaceList { + fmt.Printf("%d. %s\n", index+1, namespace) + } + + var selection int + for { + fmt.Printf("\nWhich namespace the %s installed in? (Number): ", runtimeType) + _, err := fmt.Scanf("%d", &selection) + if err != nil || selection < 1 || selection > len(namespaceList) { + fmt.Println("Invalid selection. Please enter a number corresponding to one of the listed namespaces.") + continue + } + break + } + + return namespaceList[selection-1], nil +} diff --git a/internal/k8s/runtime_resources.go b/internal/k8s/runtime_resources.go new file mode 100644 index 0000000..5c329ca --- /dev/null +++ b/internal/k8s/runtime_resources.go @@ -0,0 +1,40 @@ +package k8s + +var K8sGeneral = []string{ + "Configmaps", + "DaemonSets", + "Deployments", + "Jobs", + "Nodes", + "Pods", + "ServiceAccounts", + "Services", + "StatefulSets", +} + +var K8sClassicOnPrem = []string{ + "CronJobs", + "PersistentVolumeClaims", + "PersistentVolumes", + "Storageclass", +} + +var K8sGitOps = []string{ + "Products", + "PromotionFlows", + "PromotionPolicies", + "PromotionTemplates", + "RestrictedGitSources", +} + +var K8sArgo = []string{ + "AnalysisRuns", + "AnalysisTemplates", + "Applications", + "ApplicationSets", + "EventBus", + "EventSources", + "Experiments", + "Rollouts", + "Sensors", +} diff --git a/internal/utils/fetch_and_save.go b/internal/utils/fetch_and_save.go new file mode 100644 index 0000000..7da5a02 --- /dev/null +++ b/internal/utils/fetch_and_save.go @@ -0,0 +1,113 @@ +package utils + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/codefresh-support/codefresh-support-package/internal/k8s" +) + +func FetchAndSaveData(namespace string, k8sResources []string, dirPath, version string) error { + for _, k8sType := range k8sResources { + err := os.MkdirAll(filepath.Join(dirPath, k8sType), os.ModePerm) + if err != nil { + return fmt.Errorf("error creating directory: %v", err) + } + + fmt.Printf("Gathering %s data...\n", k8sType) + + labelSelector := "" + if k8sType == "PersistentVolumeClaims" || k8sType == "PersistentVolumes" { + labelSelector = "io.codefresh.accountName" + } + + k8sResources, err := k8s.Get(k8sType, namespace, labelSelector) + if err != nil { + fmt.Printf("Error getting %s resources: error: the server doesn't have a resource type \"%s\"\n", k8sType, strings.ToLower(k8sType)) + continue + } + + err = os.WriteFile(filepath.Join(dirPath, k8sType, fmt.Sprintf("_%sList.txt", k8sType)), []byte(k8sResources.List), os.ModePerm) + if err != nil { + return fmt.Errorf("error writing resource list: %v", err) + } + + if k8sType == "PersistentVolumeClaims" || k8sType == "PersistentVolumes" { + items, ok := k8sResources.JSON["items"].([]interface{}) + if ok && len(items) != 0 { + // Convert items to []map[string]interface{} + convertedItems := make([]map[string]interface{}, len(items)) + for i, item := range items { + if itemMap, ok := item.(map[string]interface{}); ok { + convertedItems[i] = itemMap + } else { + return fmt.Errorf("error converting item to map[string]interface{}") + } + } + + err = WriteApiCalls(convertedItems, k8sType, dirPath) + if err != nil { + return fmt.Errorf("error writing API calls: %v", err) + } + } + continue + } + + if k8sType == "Pods" { + for _, resource := range k8sResources.JSON["items"].([]interface{}) { + resourceMap := resource.(map[string]interface{}) + podName := resourceMap["metadata"].(map[string]interface{})["name"].(string) + containers := resourceMap["spec"].(map[string]interface{})["containers"].([]interface{}) + + for _, container := range containers { + containerMap := container.(map[string]interface{}) + log, err := k8s.Logs(namespace, podName, containerMap["name"].(string)) + if err != nil { + fmt.Println(err) + continue + } + + logFileName := filepath.Join(dirPath, k8sType, fmt.Sprintf("%s_%s.log", podName, containerMap["name"].(string))) + err = os.WriteFile(logFileName, []byte(log), os.ModePerm) + if err != nil { + return fmt.Errorf("error writing log file: %v", err) + } + } + } + } + + for _, resource := range k8sResources.JSON["items"].([]interface{}) { + resourceMap := resource.(map[string]interface{}) + resourceName := resourceMap["metadata"].(map[string]interface{})["name"].(string) + describeOutput, err := k8s.Describe(k8sType, namespace, resourceName) + if err != nil { + fmt.Println(err) + continue + } + + describeFileName := filepath.Join(dirPath, k8sType, fmt.Sprintf("%s.yaml", resourceName)) + err = os.WriteFile(describeFileName, []byte(describeOutput), os.ModePerm) + if err != nil { + return fmt.Errorf("error writing describe file: %v", err) + } + } + } + + events, err := k8s.Events(namespace) + if err != nil { + return fmt.Errorf("error getting k8s events: %v", err) + } + err = os.WriteFile(filepath.Join(dirPath, "Events.txt"), []byte(events), os.ModePerm) + if err != nil { + return fmt.Errorf("error writing events file: %v", err) + } + + err = os.WriteFile(filepath.Join(dirPath, "cf-support-version.txt"), []byte(version), os.ModePerm) + if err != nil { + return fmt.Errorf("error writing version file: %v", err) + } + + return nil +} diff --git a/internal/utils/prepare_package.go b/internal/utils/prepare_package.go new file mode 100644 index 0000000..e927a53 --- /dev/null +++ b/internal/utils/prepare_package.go @@ -0,0 +1,32 @@ +package utils + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "time" +) + +func PreparePackage(selectedRuntime, dirPath string) error { + supportPackageZip := fmt.Sprintf("./%s-%d.tar.gz", selectedRuntime, time.Now().Unix()) + fmt.Printf("Saving data to %s\n", supportPackageZip) + cmd := exec.Command("tar", "-czf", supportPackageZip, dirPath) + var stderr bytes.Buffer + cmd.Stderr = &stderr + err := cmd.Run() + + if err != nil { + return fmt.Errorf("error creating tar file: %v: %s", err, stderr.String()) + } + + fmt.Println("Cleaning up temp directory") + err = os.RemoveAll(dirPath) + + if err != nil { + return fmt.Errorf("error removing temp directory: %v", err) + } + + fmt.Printf("\nPlease attach %s to your support ticket.\n", supportPackageZip) + return nil +} diff --git a/internal/utils/write_api_calls.go b/internal/utils/write_api_calls.go new file mode 100644 index 0000000..5b25299 --- /dev/null +++ b/internal/utils/write_api_calls.go @@ -0,0 +1,24 @@ +package utils + +import ( + "fmt" + "os" + "path/filepath" + + "gopkg.in/yaml.v2" +) + +func WriteApiCalls(resources []map[string]interface{}, k8sType, dirPath string) error { + for _, item := range resources { + filePath := filepath.Join(dirPath, k8sType, fmt.Sprintf("%s.yaml", item["metadata"].(map[string]interface{})["name"])) + fileContent, err := yaml.Marshal(item) + if err != nil { + return fmt.Errorf("error marshaling %s resource: %v", k8sType, err) + } + err = os.WriteFile(filePath, fileContent, 0644) + if err != nil { + return fmt.Errorf("error writing %s resource to file: %v", k8sType, err) + } + } + return nil +} diff --git a/internal/utils/write_yaml_file.go b/internal/utils/write_yaml_file.go new file mode 100644 index 0000000..981de2f --- /dev/null +++ b/internal/utils/write_yaml_file.go @@ -0,0 +1,18 @@ +package utils + +import ( + "fmt" + "os" + "path/filepath" + + "gopkg.in/yaml.v2" +) + +func WriteYaml(data interface{}, name, dirPath string) error { + filePath := filepath.Join(dirPath, fmt.Sprintf("%s.yaml", name)) + fileContent, err := yaml.Marshal(data) + if err != nil { + return err + } + return os.WriteFile(filePath, fileContent, 0644) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..02a24bb --- /dev/null +++ b/main.go @@ -0,0 +1,28 @@ +/* +Copyright © 2025 Codefresh Support + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package main + +import "github.com/codefresh-support/codefresh-support-package/cmd" + +func main() { + cmd.Execute() +} diff --git a/main.js b/main.js deleted file mode 100644 index 42a3c13..0000000 --- a/main.js +++ /dev/null @@ -1,579 +0,0 @@ -'use strict'; - -import { tgz } from "jsr:@deno-library/compress"; -import { parse, stringify as toYaml } from '@std/yaml'; -import { getSemaphore } from '@henrygd/semaphore'; - -const VERSION = '__APP_VERSION__'; - -const cfRuntimeTypes = { - pipelines: 'Pipelines Runtime', - gitops: 'GitOps Runtime', - onprem: 'On-Prem', -}; - -const timestamp = new Date().getTime(); -const dirPath = `./codefresh-support-${timestamp}`; -const numOfProcesses = 5; - -// ############################## -// KUBERNETES -// ############################## - -const k8sResourceTypes = [ - 'Applications', - 'ApplicationSets', - 'Configmaps', - 'CronJobs', - 'DaemonSets', - 'Deployments', - 'Jobs', - 'Nodes', - 'PersistentVolumeClaims', - 'PersistentVolumes', - 'Pods', - 'ServiceAccounts', - 'Services', - 'StatefulSets', - 'Storageclass', -]; - -async function getK8sNamespace() { - const namespaces = new Deno.Command('kubectl', { - args: ['get', 'namespaces', '-o', 'jsonpath={.items[*].metadata.name}'], - }); - const result = await namespaces.output(); - - if (result.stderr.length > 0) { - console.error('Unable to get namespaces:'); - throw new Error(new TextDecoder().decode(result.stderr)); - } - - const namespaceList = new TextDecoder().decode(result.stdout).split(' '); - namespaceList.forEach((namespace, index) => { - console.log(`${index + 1}. ${namespace}`); - }); - - let selection; - do { - selection = Number(prompt('\nWhich Namespace Is Codefresh Installed In? (Number): ')); - if (isNaN(selection) || selection < 1 || selection > namespaceList.length) { - console.log('Invalid selection. Please enter a number corresponding to one of the listed namespaces.'); - } - } while (isNaN(selection) || selection < 1 || selection > namespaceList.length); - - return namespaceList[selection - 1]; -} - -async function getK8sResources(k8sType, namespace, labelSelector) { - try { - const cmdList = new Deno.Command('kubectl', { - args: ['get', k8sType.toLowerCase(), '-n', namespace, '-l', labelSelector], - }); - const cmdJSON = new Deno.Command('kubectl', { - args: ['get', k8sType.toLowerCase(), '-n', namespace, '-l', labelSelector, '-o', 'json'], - }); - const cmdListResult = await cmdList.output(); - const cmdJSONResult = await cmdJSON.output(); - return { - resourceList: new TextDecoder().decode( - cmdListResult.stderr.length > 0 ? cmdListResult.stderr : cmdListResult.stdout, - ), - resourceJSON: JSON.parse( - new TextDecoder().decode(cmdJSONResult.stderr.length > 0 ? cmdJSONResult.stderr : cmdJSONResult.stdout), - ), - }; - } catch (error) { - throw new Error(`Error getting ${k8sType} resources:`, error); - } -} - -async function getK8sEvents(namespace) { - try { - const events = new Deno.Command('kubectl', { - args: ['get', 'events', '-n', namespace, '--sort-by=.metadata.creationTimestamp'], - }); - const result = await events.output(); - return new TextDecoder().decode(result.stderr.length > 0 ? result.stderr : result.stdout); - } catch (error) { - throw new Error('Error getting k8s events:', error); - } -} - -async function describeK8sResources(k8sType, namespace, resourceName) { - try { - const describe = new Deno.Command('kubectl', { - args: ['describe', k8sType.toLowerCase(), '-n', namespace, resourceName], - }); - const result = await describe.output(); - return new TextDecoder().decode(result.stderr.length > 0 ? result.stderr : result.stdout); - } catch (error) { - throw new Error(`Error describing ${k8sType} resource:`, error); - } -} - -async function getK8sLogs(namespace, podName, containerName) { - try { - const logs = new Deno.Command('kubectl', { - args: ['logs', '-n', namespace, podName, '-c', containerName], - }); - const result = await logs.output(); - return new TextDecoder().decode(result.stderr.length > 0 ? result.stderr : result.stdout); - } catch (error) { - throw new Error(`Error getting logs for ${podName} - ${containerName}:`, error); - } -} - -// ############################## -// CODEFRESH -// ############################## - -async function getCodefreshCredentials() { - const envToken = Deno.env.get('CF_API_KEY'); - const envUrl = Deno.env.get('CF_BASE_URL'); - - if (envToken && envUrl) { - return { - headers: { Authorization: envToken }, - baseUrl: `${envUrl}/api`, - }; - } - - const configPath = Deno.build.os === 'windows' - ? `${Deno.env.get('USERPROFILE')}/.cfconfig` - : `${Deno.env.get('HOME')}/.cfconfig`; - - const configFileContent = await Deno.readTextFile(configPath); - const config = parse(configFileContent); - const currentContext = config.contexts?.[config['current-context']]; - - if (!currentContext) { - throw new Error('Current context not found in Codefresh config.'); - } - - return { - headers: { Authorization: currentContext.token }, - baseUrl: `${currentContext.url}/api`, - }; -} - -function getUserRuntimeSelection() { - const runtimes = Object.values(cfRuntimeTypes); - - runtimes.forEach((runtimeName, index) => { - console.log(`${index + 1}. ${runtimeName}`); - }); - - let selection; - do { - selection = Number(prompt('\nWhich Type Of Runtime Are We Using? (Enter the number):')); - if (isNaN(selection) || selection < 1 || selection > runtimes.length) { - console.log('Invalid selection. Please enter a number corresponding to one of the listed options.'); - } - } while (isNaN(selection) || selection < 1 || selection > runtimes.length); - - return runtimes[selection - 1]; -} - -// ############################## -// CODEFRESH PIPELINES -// ############################## -async function getAccountRuntimes(cfConfig) { - const response = await fetch(`${cfConfig.baseUrl}/runtime-environments`, { - method: 'GET', - headers: cfConfig.headers, - }); - const runtimes = await response.json(); - return runtimes; -} - -async function runTestPipeline(cfConfig, runtimeName) { - let selection = String( - prompt( - '\nTo troubleshoot, we would like to create a Demo Pipeline and run it.\n\nWould you like to proceed with the demo pipeline? (y/n): ', - ), - ); - while (selection !== 'y' && selection !== 'n') { - console.log('Invalid selection. Please enter "y" or "n".'); - selection = String(prompt('\nWould you like to proceed with the demo pipeline? (y/n): ')); - } - if (selection === 'n') { - return; - } - - console.log(`\nCreating a demo pipeline to test the ${runtimeName} runtime.`); - - const projectName = 'CODEFRESH-SUPPORT-PACKAGE'; - const pipelineName = 'TEST-PIPELINE-FOR-SUPPORT'; - const pipelineYaml = - 'version: "1.0"\n\nsteps:\n\n freestyle:\n title: Running test\n type: freestyle\n arguments:\n image: alpine\n commands:\n - echo "Hello Test"'; - - const project = JSON.stringify({ - projectName: projectName, - }); - - const pipeline = JSON.stringify({ - version: '1.0', - kind: 'pipeline', - metadata: { - name: `${projectName}/${pipelineName}`, - project: projectName, - originalYamlString: pipelineYaml, - }, - spec: { - concurrency: 1, - runtimeEnvironment: { - name: runtimeName, - }, - }, - }); - - const createProjectResponse = await fetch(`${cfConfig.baseUrl}/projects`, { - method: 'POST', - headers: { - ...cfConfig.headers, - 'Content-Type': 'application/json', - }, - body: project, - }); - - const projectStatus = await createProjectResponse.json(); - - if (!createProjectResponse.ok) { - const getProjectID = await fetch(`${cfConfig.baseUrl}/projects/name/${projectName}`, { - method: 'GET', - headers: cfConfig.headers, - }); - const projectResponse = await getProjectID.json(); - projectStatus.id = projectResponse.id; - } - - const createPipelineResponse = await fetch(`${cfConfig.baseUrl}/pipelines`, { - method: 'POST', - headers: { - ...cfConfig.headers, - 'Content-Type': 'application/json', - }, - body: pipeline, - }); - - const pipelineStatus = await createPipelineResponse.json(); - - if (!createPipelineResponse.ok) { - try { - const getPipelineID = await fetch(`${cfConfig.baseUrl}/pipelines/${projectName}%2f${pipelineName}`, { - method: 'GET', - headers: cfConfig.headers, - }); - const pipelineResponse = await getPipelineID.json(); - pipelineStatus.metadata = {}; - pipelineStatus.metadata.id = pipelineResponse.metadata.id; - pipelineResponse.spec.runtimeEnvironment = pipelineResponse.spec.runtimeEnvironment || {}; - pipelineResponse.spec.runtimeEnvironment.name = runtimeName; - - await fetch(`${cfConfig.baseUrl}/pipelines/${projectName}%2f${pipelineName}`, { - method: 'PUT', - headers: { - ...cfConfig.headers, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(pipelineResponse), - }); - } catch (error) { - throw new Error('Error getting / updating pipeline:', error); - } - } - - const runPipelineResponse = await fetch(`${cfConfig.baseUrl}/pipelines/run/${pipelineStatus.metadata.id}`, { - method: 'POST', - headers: { - ...cfConfig.headers, - 'Content-Type': 'application/json', - }, - }); - - const runPipelineStatus = await runPipelineResponse.json(); - - if (!runPipelineResponse.ok) { - try { - console.error('Error running pipeline:', runPipelineResponse.statusText); - console.error(runPipelineStatus); - return { pipelineID: pipelineStatus.metadata.id, projectID: projectStatus.id }; - } catch (error) { - throw new Error('Error running pipeline:', error); - } - } - - console.log(`\nDemo pipeline created and running build with id of ${runPipelineStatus}.`); - - // Wait 30 seconds to allow the pipeline to run - await new Promise((resolve) => setTimeout(resolve, 30000)); - - return { pipelineID: pipelineStatus.metadata.id, projectID: projectStatus.id, buildID: runPipelineStatus }; -} - - -async function gatherPipelinesRuntime(cfConfig) { - try { - const runtimes = await getAccountRuntimes(cfConfig); - console.log(''); - runtimes.forEach((re, index) => { - console.log(`${index + 1}. ${re.metadata.name}`); - }); - - let namespace; - let reSpec; - let pipelineExecutionOutput; - - if (runtimes.length !== 0) { - let selection; - do { - selection = Number(prompt('\nWhich Pipelines Runtime Are We Working With? (Number): ')); - if (isNaN(selection) || selection < 1 || selection > runtimes.length) { - console.log('Invalid selection. Please enter a number corresponding to one of the listed runtimes.'); - } - } while (isNaN(selection) || selection < 1 || selection > runtimes.length); - - reSpec = runtimes[selection - 1]; - namespace = reSpec.runtimeScheduler.cluster.namespace; - - pipelineExecutionOutput = await runTestPipeline(cfConfig, reSpec.metadata.name); - } else { - console.log('No Pipelines Runtimes found in the account.'); - namespace = await getK8sNamespace(); - } - - console.log( - `\nGathering Data For ${reSpec?.metadata.name ?? 'Pipelines Runtime'} in the "${namespace}" namespace.`, - ); - - await fetchAndSaveData(namespace); - - if (reSpec) { - await writeCodefreshFiles(reSpec, 'pipelines-runtime-spec'); - } - - console.log('\nData Gathered Successfully.'); - - if (pipelineExecutionOutput) { - await Deno.writeTextFile(`${dirPath}/testPipelineBuildId.txt`, pipelineExecutionOutput.buildID); - } - - await prepareAndCleanup('pipelines'); - } catch (error) { - throw new Error('Error gathering Pipelines Runtime data:', error); - } -} - -// ############################## -// CODEFRESH GITOPS -// ############################## -async function gatherGitopsRuntime() { - try { - const namespace = await getK8sNamespace(); - console.log(`\nGathering data in "${namespace}" namespace for the GitOps Runtime.`); - await fetchAndSaveData(namespace); - console.log('\nData Gathered Successfully.'); - await prepareAndCleanup('gitops'); - } catch (error) { - throw new Error(`Error gathering GitOps runtime data:`, error); - } -} -// ############################## -// CODEFRESH ONPREM -// ############################## -async function getAllAccounts(cfConfig) { - const response = await fetch(`${cfConfig.baseUrl}/admin/accounts`, { - method: 'GET', - headers: cfConfig.headers, - }); - const accounts = await response.json(); - await writeCodefreshFiles(accounts, 'onPrem-accounts'); -} - -async function getAllRuntimes(cfConfig) { - const response = await fetch(`${cfConfig.baseUrl}/admin/runtime-environments`, { - method: 'GET', - headers: cfConfig.headers, - }); - const onPremRuntimes = await response.json(); - await writeCodefreshFiles(onPremRuntimes, 'onPrem-runtimes'); -} - -async function getTotalUsers(cfConfig) { - const response = await fetch(`${cfConfig.baseUrl}/admin/user?limit=1&page=1`, { - method: 'GET', - headers: cfConfig.headers, - }); - const users = await response.json(); - await writeCodefreshFiles({ total: users.total }, 'onPrem-totalUsers'); -} - -async function getSystemFeatureFlags(cfConfig) { - const response = await fetch(`${cfConfig.baseUrl}/admin/features`, { - method: 'GET', - headers: cfConfig.headers, - }); - const onPremSystemFF = await response.json(); - await writeCodefreshFiles(onPremSystemFF, 'onPrem-systemFeatureFlags'); -} - -async function gatherOnPrem(cfConfig) { - if (cfConfig.baseUrl === 'https://g.codefresh.io/api') { - console.error( - `\nCannot gather On-Prem data for Codefresh SaaS. Please select either ${cfRuntimeTypes.pipelines} or ${cfRuntimeTypes.gitops}.\n If you need to gather data for Codefresh On-Prem, please update your ./cfconfig context (or Envs) to point to an On-Prem instance.`, - ); - throw new Error('Invalid Codefresh On-Prem URL.'); - } - - try { - const namespace = await getK8sNamespace(); - console.log(`\nGathering data in "${namespace}" namespace for Codefresh On-Prem.`); - - await fetchAndSaveData(namespace); - - await Promise.all([ - getAllAccounts(cfConfig), - getAllRuntimes(cfConfig), - getTotalUsers(cfConfig), - getSystemFeatureFlags(cfConfig), - ]); - - console.log('\nData Gathered Successfully.'); - await prepareAndCleanup('onprem'); - } catch (error) { - throw new Error(`Error gathering On-Prem data: ${error.message}`); - } -} - -// ############################## -// HELPER FUNCTIONS -// ############################## - -async function writeCodefreshFiles(data, name) { - const filePath = `${dirPath}/${name}.yaml`; - const fileContent = toYaml(data, { skipInvalid: true }); - await Deno.writeTextFile(filePath, fileContent); -} - -async function writeGetApiCalls(resources, k8sType) { - const sem = getSemaphore(k8sType, numOfProcesses); - await Promise.all(resources.map(async (item) => { - await sem.acquire(); - try { - const filePath = `${dirPath}/${k8sType}/${item.metadata.name}.yaml`; - const fileContent = toYaml(item, { skipInvalid: true }); - await Deno.writeTextFile(filePath, fileContent); - } finally { - sem.release(); - } - })); -} - -async function prepareAndCleanup(selectedRuntime) { - const supportPackageZip = `./cf-support-${selectedRuntime}-${timestamp}.tar.gz`; - console.log(`Saving data to ${supportPackageZip}`); - await tgz.compress(dirPath, supportPackageZip); - - console.log('Cleaning up temp directory'); - await Deno.remove(dirPath, { recursive: true }); - - console.log(`\nPlease attach ${supportPackageZip} to your support ticket.`); -} - -async function fetchAndSaveData(namespace) { - for (const k8sType of k8sResourceTypes) { - await Deno.mkdir(`${dirPath}/${k8sType}/`, { recursive: true }); - - console.log(`Gathering ${k8sType} data...`); - - const labelSelector = (k8sType === 'PersistentVolumeClaims' || k8sType === 'PersistentVolumes') - ? 'io.codefresh.accountName' - : ''; - - let resourceList; - let resourceJSON; - try { - const getResources = await getK8sResources(k8sType, namespace, labelSelector); - resourceList = getResources.resourceList; - resourceJSON = getResources.resourceJSON; - } catch (_error) { - console.error(`Error getting ${k8sType} resources: error: the server doesn't have a resource type "${k8sType.toLocaleLowerCase()}"`); - continue; - } - - await Deno.writeTextFile(`${dirPath}/${k8sType}/_${k8sType}List.txt`, resourceList); - - if (k8sType === 'PersistentVolumeClaims' || k8sType === 'PersistentVolumes') { - if (resourceJSON.items.length !== 0) { - await writeGetApiCalls(resourceJSON.items, k8sType); - } - continue; - } - - const sem = getSemaphore(k8sType, numOfProcesses); - - if (k8sType === 'Pods') { - await Promise.all( - resourceJSON.items.map(async (resource) => { - const podName = resource.metadata.name; - const containers = resource.spec.containers; - - await Promise.all(containers.map(async (container) => { - await sem.acquire(); - try { - const log = await getK8sLogs(namespace, podName, container.name); - const logFileName = `${dirPath}/${k8sType}/${podName}_${container.name}.log`; - await Deno.writeTextFile(logFileName, log); - } finally { - sem.release(); - } - })); - }), - ); - } - - await Promise.all(resourceJSON.items.map(async (resource) => { - await sem.acquire(); - try { - const describeOutput = await describeK8sResources(k8sType, namespace, resource.metadata.name); - const describeFileName = `${dirPath}/${k8sType}/${resource.metadata.name}.yaml`; - await Deno.writeTextFile(describeFileName, describeOutput); - } catch (error) { - console.error(error); - } finally { - sem.release(); - } - })); - } - - await Deno.writeTextFile(`${dirPath}/Events.txt`, await getK8sEvents(namespace)); - await Deno.writeTextFile(`${dirPath}/cf-support-version.txt`, VERSION); -} - -// ############################## -// MAIN -// ############################## -async function main() { - try { - console.log(`App Version: ${VERSION}\n`); - const runtimeSelected = getUserRuntimeSelection(); - const cfConfig = await getCodefreshCredentials(); - - switch (runtimeSelected) { - case cfRuntimeTypes.pipelines: - await gatherPipelinesRuntime(cfConfig); - break; - case cfRuntimeTypes.gitops: - await gatherGitopsRuntime(); - break; - case cfRuntimeTypes.onprem: - await gatherOnPrem(cfConfig); - break; - } - } catch (error) { - console.error(`Error:`, error); - } -} - -await main();