634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published
637 | by the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # :sun_behind_large_cloud: k8f :sun_behind_large_cloud:
4 |
5 |
6 |
7 | **k8f** is a command-line tool designed to simplify and streamline Kubernetes cluster operations.
8 | It provides a collection of useful commands and features that assist in managing and interacting with Kubernetes clusters efficiently.
9 | The tool was designed to scan all you're Azure and/or AWS Accounts for Kubernetes with a single command.
10 |
11 | **What can it do??**
12 | you can **Add** or **Update** EKS/AKS to your kubeconfig file.
13 | you can get you're EKS/AKS output with **k8s name**, **account**, **region**, **version**, and **upgrade status**.
14 |
15 | ## Table of Contents
16 |
17 | - [:sun\_behind\_large\_cloud: k8f :sun\_behind\_large\_cloud:](#sun_behind_large_cloud-k8f-sun_behind_large_cloud)
18 | - [Table of Contents](#table-of-contents)
19 | - [prerequisite](#prerequisite)
20 | - [Supported Platform](#supported-platform)
21 | - [Known issues](#known-issues)
22 | - [Commands](#commands)
23 | - [list](#list)
24 | - [connect](#connect)
25 | - [find](#find)
26 | - [How to install](#how-to-install)
27 | - [Windows](#windows)
28 | - [Linux](#linux)
29 | - [MacOS](#macos)
30 | - [Arm Processor](#arm-processor)
31 | - [Intel Processor](#intel-processor)
32 | - [Container](#container)
33 |
34 |
35 |
36 |
37 | > image created Using MidJorny
38 |
39 | >
40 | [](https://github.com/AdamRussak/k8f/actions/workflows/codeql-analysis.yml) [](https://github.com/AdamRussak/k8f/actions/workflows/release-new-version.yaml)    [](https://sonarcloud.io/summary/new_code?id=AdamRussak_k8f) [](https://sonarcloud.io/summary/new_code?id=AdamRussak_k8f)
41 | [](https://hub.docker.com/r/unsoop/k8f)
42 |
43 |
44 |
45 |
46 | [badge-info]: https://raw.githubusercontent.com/AdamRussak/public-images/main/badges/info.svg 'Info'
47 |
48 | > ![badge-info][badge-info]
49 | > Tested with:
50 | > AWS CLI: 2.9.17
51 | > AZ CLI: 2.44.1
52 | > Kubectl: v1.26.1
53 |
54 | ## prerequisite
55 | - for Azure: installed and logged in azure cli
56 | - for AWS: install AWS cli and Profiles for each Account at `~/.aws/credentials` and `~/.aws/config`
57 | - for GCP: Installed gcloud cli and logged in
58 |
59 | ## Supported Platform
60 |
61 |
62 | | Provider | CLI | Docker |
63 | |----------|----------|----------|
64 | | AWS | ☑ | ☑ |
65 | | Azure | ☑ | |
66 | | GCP | ☑ | |
67 |
68 | ### Known issues
69 | * GCP currently only supports the List command
70 | * Azure accounts with MFA enabled can cause failure
71 |
72 | ## Commands
73 |
74 | ### list
75 | ```sh
76 | List all K8S in Azure/AWS or Both
77 |
78 | Usage:
79 | k8f list [flags]
80 |
81 | Examples:
82 | k8f list {aws/azure/all}
83 |
84 | Flags:
85 | -h, --help help for list
86 | -o, --output string Set output type(json or yaml) (default "json")
87 | -p, --path string Set output path (default "./output")
88 | --profile-select Get UI to select single profile to connect
89 | -s, --save Get UI to select single profile to connect
90 |
91 | Global Flags:
92 | --aws-region string Set Default AWS Region (default "eu-west-1")
93 | --validate Fail on validation of the AWS credentals before running
94 | -v, --verbose verbose logging
95 | ```
96 |
97 | List Command Sample Output:
98 |
99 | [](https://raw.githubusercontent.com/AdamRussak/public-images/main/k8f/k8f-list.jpg "Sample of List command output")
100 |
101 | ### connect
102 | ```sh
103 | Connect to all the clusters of a provider or all Supported Providers
104 |
105 | Usage:
106 | k8f connect [flags]
107 |
108 | Examples:
109 | k8f connect aws -p ./testfiles/config --backup -v
110 | k8f connect aws --isEnv -p ./testfiles/config --overwrite --backup --role-name "test role" -v
111 |
112 | Flags:
113 | --auth change from AWS CLI Auth to AWS IAM Authenticator, Default set to AWS CLI
114 | --backup If true, backup config file to $HOME/.kube/config.bk
115 | --dry-run If true, only run a dry-run with cli output
116 | --force-merge If set, all duplication will be merged without prompt, default is interactive
117 | -h, --help help for connect
118 | --isEnv Add AWS Profile as Env to the Kubeconfig
119 | --merge If true, add new K8s to the existing kubeconfig path
120 | -o, --output string kubeconfig output type format(json or yaml) (default "yaml")
121 | --overwrite If true, force overwrite kubeconfig
122 | -p, --path string Set output path (default "/home//.kube/config")
123 | --profile-select provides a UI to select a single profile to scan
124 | --role-name string Set Role Name (Example: 'myRoleName')
125 | -s, --short-name shorten EKS name from :: to :
126 |
127 | Global Flags:
128 | --aws-region string Set Default AWS Region (default "eu-west-1")
129 | --validate Fail on validation of the AWS credentals before running
130 | -v, --verbose verbose logging
131 | ```
132 |
133 | ### find
134 | ```sh
135 | Find if a specific K8S exist in Azure or AWS
136 |
137 | Usage:
138 | k8f find [flags]
139 |
140 | Examples:
141 | k8f find {aws/azure/all} my-k8s-cluster
142 |
143 | Flags:
144 | -h, --help help for find
145 |
146 | Global Flags:
147 | --aws-region string Set Default AWS Region (default "eu-west-1")
148 | --validate Fail on validation of the AWS credentals before running
149 | -v, --verbose verbose logging
150 | ```
151 |
152 | ## How to install
153 |
154 | ### Windows
155 | Latest:
156 | ```ps
157 | $downloads = "$env:USERPROFILE\Downloads"
158 | $source = "$downloads\k8f.exe"
159 | $destination = "C:\tool"
160 | Invoke-WebRequest -Uri "https://github.com/AdamRussak/k8f/releases/latest/download/k8f.exe" -OutFile $source
161 | New-Item -ItemType Directory -Path $destination
162 | Copy-Item -Path $source -Destination $destination
163 | [Environment]::SetEnvironmentVariable("Path", "$env:Path;$destination\k8f.exe", "Machine")
164 | ```
165 | Version:
166 | ```ps
167 | $downloads = "$env:USERPROFILE\Downloads"
168 | $source = "$downloads\k8f.exe"
169 | $destination = "C:\tool"
170 | $version = "0.3.1"
171 | Invoke-WebRequest -Uri "https://github.com/AdamRussak/k8f/releases/download/$version/k8f.exe" -OutFile "$downloads\k8f.exe"
172 | New-Item -ItemType Directory -Path $destination
173 | Copy-Item -Path $source -Destination $destination
174 | [Environment]::SetEnvironmentVariable("Path", "$env:Path;$destination\k8f.exe", "Machine")
175 | ```
176 | ### Linux
177 | Latest:
178 | ```sh
179 | cd ~ && wget https://github.com/AdamRussak/k8f/releases/latest/download/k8f
180 | sudo cp ~/k8f /usr/local/bin/k8f
181 | sudo chmod 755 /usr/local/bin/k8f
182 | ```
183 | Version:
184 | ```sh
185 | cd ~ && wget https://github.com/AdamRussak/k8f/releases/download//k8f
186 | sudo cp ~/k8f /usr/local/bin/k8f
187 | sudo chmod 755 /usr/local/bin/k8f
188 | ```
189 | ### MacOS
190 | #### Arm processor
191 | Latest:
192 | ```sh
193 | cd ~ && wget https://github.com/AdamRussak/k8f/releases/latest/download/k8f_darwin-arm64
194 | mv k8f_darwin-arm64 ./k8f
195 | sudo cp ~/k8f /usr/local/bin/k8f
196 | sudo chmod 755 /usr/local/bin/k8f
197 | ```
198 | Version:
199 | ```sh
200 | cd ~ && wget https://github.com/AdamRussak/k8f/releases/download//k8f_darwin-arm64
201 | mv k8f_darwin-arm64 ./k8f
202 | sudo cp ~/k8f /usr/local/bin/k8f
203 | sudo chmod 755 /usr/local/bin/k8f
204 | ```
205 | #### Intel processor
206 | Latest:
207 | ```sh
208 | cd ~ && wget https://github.com/AdamRussak/k8f/releases/latest/download/k8f_darwin-amd64
209 | mv k8f_darwin-amd64 ./k8f
210 | sudo cp ~/k8f /usr/local/bin/k8f
211 | sudo chmod 755 /usr/local/bin/k8f
212 | ```
213 | Version:
214 | ```sh
215 | cd ~ && wget https://github.com/AdamRussak/k8f/releases/download//k8f_darwin-amd64
216 | mv k8f_darwin-amd64 ./k8f
217 | sudo cp ~/k8f /usr/local/bin/k8f
218 | sudo chmod 755 /usr/local/bin/k8f
219 | ```
220 |
221 | ### Container
222 | ```sh
223 | # Basic
224 | docker run -v {path to .aws directory}:/home/nonroot/.aws/:ro unsoop/k8f:
225 |
226 | # Automation Queryable output
227 | OUTPUT=$(docker run -v {path to .aws directory}:/home/nonroot/.aws/:ro unsoop/k8f: 2> /dev/null | grep -o '{.*}')
228 |
229 | ```
230 |
--------------------------------------------------------------------------------
/cmd/connect.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2022 NAME HERE
3 | */
4 | package cmd
5 |
6 | import (
7 | "errors"
8 | "k8f/core"
9 | "k8f/provider"
10 | "os"
11 |
12 | log "github.com/sirupsen/logrus"
13 | "github.com/spf13/cobra"
14 | )
15 |
16 | // FEATURE: add flag to support aws commands per account/profile (for example: ask per-account what to use)
17 | // connectCmd represents the connect command
18 | var (
19 | connectCmd = &cobra.Command{
20 | Use: connectCMD,
21 | Short: connectShort,
22 | Example: connectExample,
23 | Run: func(cmd *cobra.Command, args []string) {
24 | var options provider.CommandOptions = newCommandStruct(cmd, o, args)
25 | log.WithField("CommandOptions", log.Fields{"struct": core.DebugWithInfo(options)}).Debug("CommandOptions Struct Keys and Values: ")
26 | if !options.Overwrite && core.Exists(options.Path) && !options.DryRun && !options.Backup && !options.Merge {
27 | core.FailOnError(errors.New("flags error"), "Cant Run command as path exist and Overwrite is set to FALSE")
28 | }
29 | if !core.Exists(options.Path) {
30 | core.CreateDirectory(options.Path)
31 | log.Warn("Path directorys were created")
32 | }
33 | if !core.Exists(options.Path) && (options.Backup || options.Merge) {
34 | fileCreateConfirm := provider.BoolUI("--backup/--merge option was used but "+options.Path+
35 | " does not exist. Choose True to create empty file, if False is chosen backup/merge will fail", options)
36 | if fileCreateConfirm == "True" {
37 | err := os.WriteFile(options.Path, []byte(""), 0666)
38 | core.FailOnError(err, "failed to save config")
39 | } else {
40 | core.FailOnError(errors.New("flags error"), "Cant Run command as path does not exist and Backup or Merge is set to True")
41 | }
42 | }
43 | if args[0] == "azure" {
44 | options.ConnectAllAks()
45 | } else if args[0] == "aws" {
46 | options.ConnectAllEks()
47 | } else if args[0] == "all" {
48 | log.Info("Supported Platform are:" + core.PrintOutStirng(supportedProvider))
49 | options.FullCloudConfig()
50 | } else {
51 | core.FailOnError(errors.New("no Provider Selected"), "Selected Provider Not avilable (yet)")
52 | }
53 | },
54 | }
55 | )
56 |
57 | func init() {
58 | connectCmd.Flags().StringVarP(&o.Output, "output", "o", defaultYAMLoutput, "kubeconfig output type format(json or yaml)")
59 | connectCmd.Flags().StringVarP(&o.Path, "path", "p", confPath, "Set output path")
60 | connectCmd.Flags().BoolVar(&o.ProfileSelector, "profile-select", false, "provides a UI to select a single profile to scan")
61 | connectCmd.Flags().BoolVar(&o.Overwrite, "overwrite", false, "If true, force overwrite kubeconfig")
62 | connectCmd.Flags().BoolVar(&o.DryRun, DryRun, false, "If true, only run a dry-run with cli output")
63 | connectCmd.Flags().BoolVar(&o.Backup, "backup", false, "If true, backup config file to $HOME/.kube/config.bk")
64 | connectCmd.Flags().BoolVar(&o.Merge, "merge", false, "If true, add new K8s to the existing kubeconfig path")
65 | connectCmd.Flags().BoolVar(&o.ForceMerge, "force-merge", false, "If set, all duplication will be merged without prompt, default is interactive")
66 | connectCmd.Flags().BoolVar(&o.AwsAuth, "auth", false, "change from AWS CLI Auth to AWS IAM Authenticator, Default set to AWS CLI")
67 | connectCmd.Flags().BoolVar(&o.AwsEnvProfile, "isEnv", false, "Add AWS Profile as Env to the Kubeconfig")
68 | connectCmd.Flags().BoolVarP(&o.AwsClusterName, "short-name", "s", false, "shorten EKS name from :: to :")
69 | connectCmd.Flags().StringVar(&o.AwsRoleString, "role-name", "", "Set Role Name (Example: 'myRoleName')")
70 | connectCmd.MarkFlagsMutuallyExclusive(DryRun, "overwrite", "backup", "merge")
71 | rootCmd.AddCommand(connectCmd)
72 | }
73 |
--------------------------------------------------------------------------------
/cmd/const.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | const (
4 | // global
5 | providerError = "requires cloud provider"
6 | providerListError = "invalid cloud provider specified: %s"
7 | // connect
8 | connectCMD = "connect"
9 | connectShort = "Connect to all the clusters of a provider or all Supported Providers"
10 | connectExample = `k8f connect aws -p ./testfiles/config --backup -v
11 | k8f connect aws --isEnv -p ./testfiles/config --overwrite --backup --role-name "test role" -v`
12 |
13 | //find
14 | findCMD = "find"
15 | findShort = "Find if a specific K8S exist in Azure or AWS"
16 | findExample = `k8f find {aws/azure/all} my-k8s-cluster`
17 | //list
18 | listCMD = "list"
19 | listShort = "List all K8S in Azure/AWS or Both"
20 | listExample = `k8f list {aws/azure/all}`
21 | )
22 |
--------------------------------------------------------------------------------
/cmd/constant.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | const DryRun = "dry-run"
4 | const triggered = "triggered "
5 | const validationError = "validation failed"
6 |
--------------------------------------------------------------------------------
/cmd/core.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "k8f/core"
7 | "k8f/provider"
8 |
9 | "github.com/spf13/cobra"
10 | )
11 |
12 | func newCommandStruct(cmd *cobra.Command, o FlagsOptions, args []string) provider.CommandOptions {
13 | var commandPath string
14 | var commandOutput string
15 | switch cmd.Use {
16 | case "list":
17 | commandPath = o.ListPath
18 | commandOutput = o.ListOutput
19 | default:
20 | commandPath = o.Path
21 | commandOutput = o.Output
22 | }
23 | var commandOptions provider.CommandOptions = provider.CommandOptions{
24 | AwsRegion: AwsRegion,
25 | ForceMerge: o.ForceMerge,
26 | UiSize: o.UiSize,
27 | Path: commandPath,
28 | Output: commandOutput,
29 | Overwrite: o.Overwrite,
30 | Combined: core.IfXinY(args[0], supportedProvider),
31 | Merge: o.Merge,
32 | Backup: o.Backup,
33 | DryRun: o.DryRun,
34 | AwsAuth: o.AwsAuth,
35 | AwsRoleString: o.AwsRoleString,
36 | AwsEnvProfile: o.AwsEnvProfile,
37 | AwsClusterName: o.AwsClusterName,
38 | ProfileSelector: o.ProfileSelector,
39 | SaveOutput: o.SaveOutput,
40 | Validate: o.Validate,
41 | }
42 |
43 | return commandOptions
44 | }
45 |
46 | func argValidator(cmd *cobra.Command, args []string) error {
47 | var err error
48 | err = checkArgsCount(args)
49 | core.FailOnError(err, validationError)
50 | err = providerValidator(args, cmd)
51 | core.FailOnError(err, validationError)
52 | err = uiSelectValidator(args)
53 | core.FailOnError(err, validationError)
54 | return err
55 | }
56 |
57 | // check amounts of args in the command
58 | func checkArgsCount(args []string) error {
59 | if len(args) < 1 {
60 | return errors.New(providerError)
61 | }
62 | return nil
63 | }
64 |
65 | // check the args for supported Provider
66 | func providerValidator(args []string, cmd *cobra.Command) error {
67 | argouments = append(argouments, supportedProvider...)
68 | switch cmd.Use {
69 | case "find":
70 | if !core.IfXinY(args[0], argouments) {
71 | return fmt.Errorf(providerListError, args[0])
72 | }
73 | default:
74 | if len(args) > 0 && len(args) <= len(argouments) {
75 | for a := range args {
76 | if !core.IfXinY(args[a], argouments) {
77 | return fmt.Errorf(providerListError, args[a])
78 | }
79 | }
80 | }
81 | }
82 | return nil
83 | }
84 |
85 | // check if aws UI select was set
86 | func uiSelectValidator(args []string) error {
87 | if o.ProfileSelector && args[0] != "aws" {
88 | return fmt.Errorf("profile selector supports only AWS and is not supporting - %s", args[0])
89 | }
90 | return nil
91 | }
92 |
--------------------------------------------------------------------------------
/cmd/dd.go:
--------------------------------------------------------------------------------
1 | // /*
2 | // Copyright © 2022 NAME HERE
3 |
4 | // */
5 | package cmd
6 |
7 | // import (
8 | // "fmt"
9 | // "k8f/tools"
10 |
11 | // "github.com/spf13/cobra"
12 | // )
13 |
14 | // // ddCmd represents the dd command
15 | // var apikey string
16 | // var appkey string
17 | // var ddCmd = &cobra.Command{
18 | // Use: "dd",
19 | // Short: "Send Metrics to Data Dog",
20 | // // Args: func(cmd *cobra.Command, args []string) error {
21 | // // if len(args) < 1 {
22 | // // return errors.New("requires cloud provider")
23 | // // }
24 | // // argouments = append(argouments, supportedProvider...)
25 |
26 | // // if core.IfXinY(args[0], argouments) {
27 | // // return nil
28 | // // }
29 | // // return fmt.Errorf("invalid cloud provider specified: %s", args[0])
30 | // // },
31 | // Run: func(cmd *cobra.Command, args []string) {
32 | // fmt.Println("apikey: " + apikey)
33 | // tools.DdMain(apikey)
34 | // fmt.Println("dd called")
35 | // },
36 | // }
37 |
38 | // func init() {
39 | // ddCmd.Flags().StringVar(&apikey, "apikey", "", "Set API Key for Datadog")
40 | // rootCmd.AddCommand(ddCmd)
41 | // }
42 |
--------------------------------------------------------------------------------
/cmd/find.go:
--------------------------------------------------------------------------------
1 | // /*
2 | // Copyright © 2022 NAME HERE
3 |
4 | // */
5 | package cmd
6 |
7 | import (
8 | "errors"
9 | "fmt"
10 | "k8f/core"
11 | "k8f/provider"
12 |
13 | log "github.com/sirupsen/logrus"
14 | "github.com/spf13/cobra"
15 | )
16 |
17 | // findCmd represents the find command
18 | var findCmd = &cobra.Command{
19 | Use: findCMD,
20 | Short: findShort,
21 | Example: findExample,
22 | Run: func(cmd *cobra.Command, args []string) {
23 | var p provider.Cluster
24 | var options provider.CommandOptions = newCommandStruct(cmd, o, args)
25 | log.WithField("CommandOptions", log.Fields{"struct": core.DebugWithInfo(options)}).Debug("CommandOptions Struct Keys and Values: ")
26 | log.Info("find called")
27 | if args[0] == "azure" {
28 | p = options.GetSingleAzureCluster(args[1])
29 | } else if args[0] == "aws" {
30 | p = options.GetSingleAWSCluster(args[1])
31 | // TODO: add find single cluster to all (in case we know name but not platform)
32 | } else if args[0] == "all" {
33 | log.Info("Supported Platform are:" + core.PrintOutStirng(supportedProvider))
34 | // p = options.FullCloudConfig()
35 | } else {
36 | core.FailOnError(errors.New("no Provider Selected"), "Selected Provider Not avilable (yet)")
37 | }
38 | log.Debug(string("Outputing List as " + options.Output + " Format"))
39 | var output, err = options.PrintoutResults(p)
40 | core.FailOnError(err, "failed to print results")
41 | fmt.Println(output)
42 | },
43 | }
44 |
45 | func init() {
46 | rootCmd.AddCommand(findCmd)
47 | }
48 |
--------------------------------------------------------------------------------
/cmd/list.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2022 NAME HERE
3 | */
4 | package cmd
5 |
6 | import (
7 | "fmt"
8 | "k8f/core"
9 | "k8f/provider"
10 |
11 | log "github.com/sirupsen/logrus"
12 | "github.com/spf13/cobra"
13 | )
14 |
15 | // listCmd represents the list command
16 |
17 | var listCmd = &cobra.Command{
18 | Use: listCMD,
19 | Short: listShort,
20 | Example: listExample,
21 | Run: func(cmd *cobra.Command, args []string) {
22 | var list []provider.Provider
23 | var p interface{}
24 | var options provider.CommandOptions = newCommandStruct(cmd, o, args)
25 | log.WithField("CommandOptions", log.Fields{"struct": core.DebugWithInfo(options)}).Debug("CommandOptions Struct Keys and Values: ")
26 | log.Debug("CommandOptions Used")
27 | if len(args) == 1 && args[0] == "azure" {
28 | log.Debug("Starting Azure List")
29 | p = options.FullAzureList()
30 | } else if len(args) == 1 && args[0] == "aws" {
31 | log.Debug("Starting AWS List")
32 | p = options.FullAwsList()
33 | } else if len(args) == 1 && args[0] == "gcp" {
34 | log.Debug("Starting GCP List")
35 | p = options.GcpMain()
36 | } else if len(args) == 1 && args[0] == "all" {
37 | log.Debug("Starting All List")
38 | log.Info("Supported Platform are:" + core.PrintOutStirng(supportedProvider))
39 |
40 | var c0 = make(chan provider.Provider)
41 | for _, s := range supportedProvider {
42 | log.Debug(string("Starting " + s + " Provider"))
43 | go runAll(c0, options, s)
44 | }
45 | for i := 0; i < len(supportedProvider); i++ {
46 | var res provider.Provider = <-c0
47 | log.Trace(string("Recived A response from: " + supportedProvider[i]))
48 | list = append(list, res)
49 | }
50 | p = list
51 | } else {
52 | log.Debug("Starting All List")
53 | log.Info("Supported Platform are:" + core.PrintOutStirng(supportedProvider))
54 | var c0 = make(chan provider.Provider)
55 | for _, s := range args {
56 | log.Debug(string("Starting " + s + " Provider"))
57 | go runAll(c0, options, s)
58 | }
59 | for i := 0; i < len(args); i++ {
60 | var res provider.Provider = <-c0
61 | log.Trace(string("Recived A response from: " + args[i]))
62 | list = append(list, res)
63 | }
64 | p = list
65 | }
66 | log.Debug(string("Outputing List as " + options.Output + " Format"))
67 | if options.SaveOutput {
68 | options.StructOutput(p)
69 | } else {
70 | var output, err = options.PrintoutResults(p)
71 | core.FailOnError(err, "failed to get printout result")
72 | fmt.Println(output)
73 | }
74 | },
75 | }
76 |
77 | func init() {
78 | listCmd.Flags().BoolVar(&o.ProfileSelector, "profile-select", false, "Get UI to select single profile to connect")
79 | listCmd.Flags().BoolVarP(&o.SaveOutput, "save", "s", false, "Get UI to select single profile to connect")
80 | listCmd.Flags().StringVarP(&o.ListOutput, "output", "o", defaultJSONoutput, "Set output type(json or yaml)")
81 | listCmd.Flags().StringVarP(&o.ListPath, "path", "p", listPath, "Set output path")
82 | rootCmd.AddCommand(listCmd)
83 |
84 | }
85 |
86 | func runAll(c0 chan provider.Provider, options provider.CommandOptions, s string) {
87 | var r provider.Provider
88 | if s == "azure" {
89 | log.Trace(string(triggered + s))
90 | r = options.FullAzureList()
91 | } else if s == "aws" {
92 | log.Trace(string(triggered + s))
93 | r = options.FullAwsList()
94 | } else if s == "gcp" {
95 | log.Trace(string(triggered + s))
96 | r = options.GcpMain()
97 | }
98 | c0 <- r
99 | }
100 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2022 NAME HERE
3 | */
4 | package cmd
5 |
6 | import (
7 | "k8f/core"
8 |
9 | "github.com/spf13/cobra"
10 | "k8s.io/client-go/tools/clientcmd"
11 | )
12 |
13 | var tversion string
14 |
15 | // rootCmd represents the base command when called without any subcommands
16 | var (
17 | o FlagsOptions
18 | supportedProvider = []string{"azure", "aws", "gcp"}
19 | argouments = []string{"all"}
20 | AwsRegion = "eu-west-1"
21 | defaultYAMLoutput = "yaml"
22 | defaultJSONoutput = "json"
23 | version = tversion
24 | confPath = clientcmd.RecommendedHomeFile
25 | listPath = "./output"
26 | rootCmd = &cobra.Command{
27 | Version: version,
28 | Use: "k8f",
29 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
30 | core.ToggleDebug()
31 | var err error = argValidator(cmd, args)
32 | core.FailOnError(err, "Validation failed")
33 | return err
34 | },
35 | Short: "A CLI tool to List, Connect, Search and check version for K8S Clusters in all your resources at once",
36 | Long: `A CLI tool to find, list, connect, search and check version for K8S Clusters in all your resources at once,
37 | this tool supports Azure AKS and AWS EKS. For example:
38 | to get List of all EKS:
39 | k8f list aws
40 | to connect to all K8S:
41 | k8f connect all`,
42 | }
43 | )
44 |
45 | // Execute adds all child commands to the root command and sets flags appropriately.
46 | // This is called by main.main(). It only needs to happen once to the rootCmd.
47 | func Execute() {
48 | var err error = rootCmd.Execute()
49 | core.FailOnError(err, "error executing command")
50 | }
51 |
52 | func init() {
53 | rootCmd.Flags().BoolVar(&o.DryRun, "dry-run", false, "Run the task as Dry-run, no action is done")
54 | rootCmd.PersistentFlags().BoolVarP(&core.Verbosity, "verbose", "v", false, "verbose logging (default false)")
55 | rootCmd.PersistentFlags().BoolVarP(&core.ErrorLevel, "quit", "q", false, "error-level logging, only errors are shown, very useful for scripts and automation (default false)")
56 | rootCmd.PersistentFlags().BoolVar(&o.Validate, "validate", false, "Fail on validation of the AWS credentals before running the command (default false)")
57 | rootCmd.PersistentFlags().StringVar(&AwsRegion, "aws-region", AwsRegion, "Set Default AWS Region")
58 | rootCmd.Flags().IntVar(&o.UiSize, "ui-size", 4, "number of list items to show in menu at once")
59 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
60 | rootCmd.MarkFlagsMutuallyExclusive("verbose", "quit")
61 | }
62 |
--------------------------------------------------------------------------------
/cmd/struct.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | type FlagsOptions struct {
4 | Path string `json:"path,omitempty"`
5 | ListPath string `json:"list_path,omitempty"`
6 | Output string `json:"output,omitempty"`
7 | ListOutput string `json:"list_output,omitempty"`
8 | Overwrite bool `json:"overwrite,omitempty"`
9 | Backup bool `json:"backup,omitempty"`
10 | Merge bool `json:"merge,omitempty"`
11 | ForceMerge bool `json:"force_merge,omitempty"`
12 | UiSize int `json:"uiSize,omitempty"`
13 | DryRun bool `json:"dry-run,omitempty"`
14 | AwsAuth bool `json:"aws_auth,omitempty"`
15 | AwsAssumeRole bool `json:"aws_assume_role,omitempty"`
16 | AwsRoleString string `json:"aws_role_string,omitempty"`
17 | AwsEnvProfile bool `json:"aws_profile,omitempty"`
18 | AwsClusterName bool `json:"aws_cluster_name,omitempty"`
19 | ProfileSelector bool `json:"profile_selector,omitempty"`
20 | SaveOutput bool `json:"save_output,omitempty"`
21 | Validate bool `json:"validate,omitempty"`
22 | }
23 |
--------------------------------------------------------------------------------
/core/core.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "fmt"
7 | "os"
8 | "path/filepath"
9 | "strings"
10 |
11 | log "github.com/sirupsen/logrus"
12 | "gopkg.in/ini.v1"
13 | )
14 |
15 | func FailOnError(err error, message string) {
16 | if err != nil {
17 | log.Errorf("%s: %s\n", message, err)
18 | os.Exit(1)
19 | }
20 | }
21 |
22 | // getEnvVarOrExit returns the value of specified environment variable or terminates if it's not defined.
23 | func CheckEnvVarOrSitIt(varName string, varKey string) {
24 | val, present := os.LookupEnv(varName)
25 | if present {
26 | log.Debug("Variable " + varName + " Was Pre-set with Value: " + val)
27 | } else {
28 | err := os.Setenv(varName, varKey)
29 | val = os.Getenv(varName)
30 | log.Debug("Variable " + varName + " is Set with Value: " + val)
31 | FailOnError(err, "Issue setting the 'AWS_REGION' Enviroment Variable")
32 | }
33 | }
34 |
35 | func PrintOutStirng(arrayOfStrings []string) string {
36 | var s string
37 | for _, t := range arrayOfStrings {
38 | s = s + " " + t
39 | }
40 | return s
41 | }
42 |
43 | func IfXinY(x string, y []string) bool {
44 | for _, t := range y {
45 | if x == t {
46 | return true
47 | }
48 | }
49 | return false
50 | }
51 |
52 | func Exists(path string) bool {
53 | log.Trace("Start Checking if Path Exist")
54 | _, err := os.Stat(path)
55 | if err == nil {
56 | log.Debug("Path Exist")
57 | return true
58 | }
59 | if os.IsNotExist(err) {
60 | log.Debug("Path Dose NOT Exist")
61 | return false
62 | }
63 | return false
64 | }
65 |
66 | func CreateDirectory(path string) {
67 | // parts := strings.Split(path, string(os.PathSeparator))
68 | dir := filepath.Dir(path)
69 | var create string
70 | if Exists(dir) {
71 | log.Trace(dir + " Path Exist")
72 | } else {
73 | log.Debug("Createing Directory: " + filepath.Dir(path))
74 | if !filepath.IsAbs(dir) {
75 | log.Debug(dir + " Path is Not Absolute")
76 | create = "./" + dir
77 | } else {
78 | create = dir
79 | }
80 | err := os.MkdirAll(create, 0777)
81 | FailOnError(err, "Failed to Create Directory")
82 | }
83 | }
84 |
85 | func MergeINIFiles(inputPaths []string) (*bytes.Reader, error) {
86 | // Create a buffer to store the merged result
87 | outputBuffer := bytes.Buffer{}
88 | // Iterate over input INI files
89 | for _, inputPath := range inputPaths {
90 | if !Exists(inputPath) && strings.Contains(inputPath, "credentials") {
91 | FailOnError(errors.New("https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html"), "AWS File `"+inputPath+"` is miss-configured, please fix it and run again")
92 | } else if !Exists(inputPath) {
93 | log.Warning("\tAWS File `"+inputPath+"` is miss-configured, some of your account might not show.\n\t please fix it and run again: ", "https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html")
94 | continue
95 | }
96 | // Open the input INI file
97 | inputFile, err := ini.InsensitiveLoad(inputPath)
98 | FailOnError(err, "failed to load INI")
99 | for _, section := range inputFile.Sections() {
100 | if !checkIfConfigExist(section.Name(), outputBuffer) && len(section.Keys()) != 0 {
101 | outputBuffer.WriteString(fmt.Sprintf("[%s]\n", section.Name()))
102 | // Iterate over keys in the section
103 | for _, key := range section.Keys() {
104 | outputBuffer.WriteString(fmt.Sprintf("%s = %s\n", key.Name(), key.Value()))
105 | }
106 | outputBuffer.WriteString("\n")
107 | }
108 | }
109 | }
110 | return bytes.NewReader(outputBuffer.Bytes()), nil
111 | }
112 |
113 | func checkIfConfigExist(sectionName string, mergingConfig bytes.Buffer) bool {
114 | if len(mergingConfig.Bytes()) == 0 {
115 | return false
116 | }
117 | wordToRemove := "profile "
118 | currentConf := strings.Replace(sectionName, wordToRemove, "", -1)
119 | inputFile, err := ini.InsensitiveLoad(bytes.NewReader(mergingConfig.Bytes()))
120 | FailOnError(err, "failed to load INI")
121 | for _, section := range inputFile.Sections() {
122 | inConfString := strings.Replace(section.Name(), wordToRemove, "", -1)
123 | if currentConf == inConfString {
124 | return true
125 | }
126 | }
127 | return false
128 | }
129 |
--------------------------------------------------------------------------------
/core/core_test.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "bytes"
5 | "os"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestCheckEnvVarOrSitIt(t *testing.T) {
12 | testCases := []struct {
13 | name string
14 | key string
15 | value string
16 | expected string
17 | }{
18 | {
19 | name: "Test_CheckEnvVarOrSitIt_EnvVarSet",
20 | key: "TEST",
21 | value: "updated",
22 | expected: "SET",
23 | },
24 | {
25 | name: "Test_CheckEnvVarOrSitIt_EnvVarNotSet",
26 | key: "TEST",
27 | value: "updated",
28 | expected: "updated",
29 | },
30 | {
31 | name: "Test_CheckEnvVarOrSitIt_EnvVarSetAndNoChangeNeeded",
32 | key: "TEST",
33 | value: "SET",
34 | expected: "SET",
35 | },
36 | }
37 | for _, tc := range testCases {
38 | t.Run(tc.name, func(t *testing.T) {
39 | var origVal = "SET"
40 | if tc.expected == origVal {
41 | t.Setenv(tc.key, origVal)
42 | }
43 | CheckEnvVarOrSitIt(tc.key, tc.value)
44 | val, present := os.LookupEnv(tc.key)
45 | if !present || val != tc.expected {
46 | t.Fatalf(`Var(%s) = %s,should have been %s`, tc.key, val, tc.expected)
47 | }
48 | })
49 | }
50 | }
51 |
52 | func TestPrintOutStirng(t *testing.T) {
53 | testCases := []struct {
54 | name string
55 | input []string
56 | expected string
57 | }{
58 | {
59 | name: "EmptyArray",
60 | input: []string{},
61 | expected: "",
62 | },
63 | {
64 | name: "OneItemArray",
65 | input: []string{"Test"},
66 | expected: " Test",
67 | },
68 | {
69 | name: "TwoItemArray",
70 | input: []string{"Test", "Test"},
71 | expected: " Test Test",
72 | },
73 | {
74 | name: "ThreeItemArray",
75 | input: []string{"Test", "Test", "Test"},
76 | expected: " Test Test Test",
77 | },
78 | {
79 | name: "FourItemArray",
80 | input: []string{"Test", "Test", "Test", "Test"},
81 | expected: " Test Test Test Test",
82 | },
83 | {
84 | name: "FiveItemArray",
85 | input: []string{"Test", "Test", "Test", "Test", "Test"},
86 | expected: " Test Test Test Test Test",
87 | },
88 | {
89 | name: "SixItemArray",
90 | input: []string{"Test", "Test", "Test", "Test", "Test", "Test"},
91 | expected: " Test Test Test Test Test Test",
92 | },
93 | {
94 | name: "SevenItemArray",
95 | input: []string{"Test", "Test", "Test", "Test", "Test", "Test", "Test"},
96 | expected: " Test Test Test Test Test Test Test",
97 | },
98 | {
99 | name: "EightItemArray",
100 | input: []string{"Test", "Test", "Test", "Test", "Test", "Test", "Test", "Test"},
101 | expected: " Test Test Test Test Test Test Test Test",
102 | },
103 | {
104 | name: "NineItemArray",
105 | input: []string{"Test", "Test", "Test", "Test", "Test", "Test", "Test", "Test", "Test"},
106 | expected: " Test Test Test Test Test Test Test Test Test",
107 | },
108 | {
109 | name: "TenItemArray",
110 | input: []string{"Test", "Test", "Test", "Test", "Test", "Test", "Test", "Test", "Test", "Test"},
111 | expected: " Test Test Test Test Test Test Test Test Test Test",
112 | },
113 | }
114 | for _, tc := range testCases {
115 | t.Run(tc.name, func(t *testing.T) {
116 | result := PrintOutStirng(tc.input)
117 | if result != tc.expected {
118 | t.Fatalf(`PrintOutStirng(%s) = %s,should have been %s`, tc.input, result, tc.expected)
119 | }
120 | })
121 | }
122 | }
123 |
124 | func TestIfXinY(t *testing.T) {
125 | testCases := []struct {
126 | name string
127 | x string
128 | y []string
129 | expected bool
130 | }{
131 | {
132 | name: "StringExistsInSlice",
133 | x: "apple",
134 | y: []string{"apple", "banana", "orange"},
135 | expected: true,
136 | },
137 | {
138 | name: "StringDoesNotExistInSlice",
139 | x: "grape",
140 | y: []string{"apple", "banana", "orange"},
141 | expected: false,
142 | },
143 | {
144 | name: "EmptySlice",
145 | x: "apple",
146 | y: []string{},
147 | expected: false,
148 | },
149 | {
150 | name: "EmptyString",
151 | x: "",
152 | y: []string{"apple", "banana", "orange"},
153 | expected: false,
154 | },
155 | {
156 | name: "EmptyStringAndEmptySlice",
157 | x: "",
158 | y: []string{},
159 | expected: false,
160 | },
161 | }
162 | for _, tc := range testCases {
163 | t.Run(tc.name, func(t *testing.T) {
164 | var result bool = IfXinY(tc.x, tc.y)
165 | if result != tc.expected {
166 | t.Errorf("Expected %q in %q to be %v, but got %v", tc.x, tc.y, tc.expected, result)
167 | }
168 | })
169 | }
170 | }
171 |
172 | func TestExists(t *testing.T) {
173 | testCases := []struct {
174 | name string
175 | path string
176 | expected bool
177 | }{
178 | {
179 | name: "PathExists",
180 | path: "existing_file.txt",
181 | expected: true,
182 | },
183 | {
184 | name: "PathDoesNotExist",
185 | path: "nonexistent_file.txt",
186 | expected: false,
187 | },
188 | {
189 | name: "PermissionDenied",
190 | path: "/root/somefile.txt",
191 | expected: false,
192 | },
193 | }
194 | for _, tc := range testCases {
195 | t.Run(tc.name, func(t *testing.T) {
196 | //create a temporary file
197 | if tc.expected {
198 | file, err := os.Create(tc.path)
199 | if err != nil {
200 | t.Fatal(err)
201 | }
202 | defer func() {
203 | file.Close()
204 | os.Remove(tc.path)
205 | }()
206 | } else {
207 | os.Remove(tc.path)
208 | }
209 | var exists bool = Exists(tc.path)
210 | if exists != tc.expected {
211 | t.Errorf("Exists(%q) = %v; expected %v", tc.path, exists, tc.expected)
212 | }
213 | })
214 | }
215 | }
216 |
217 | func TestCreateDirectory(t *testing.T) {
218 | const conf string = "/config"
219 | testCases := []struct {
220 | name string
221 | MainPath string
222 | SecondaryPath string
223 | expected bool
224 | }{
225 | {
226 | name: "DirectoryDoesNotExist",
227 | MainPath: "new_directory",
228 | SecondaryPath: conf,
229 | expected: true,
230 | },
231 | {
232 | name: "DirectoryExists",
233 | MainPath: "existing_directory",
234 | SecondaryPath: conf,
235 | expected: true,
236 | },
237 | {
238 | name: "PermissionDenied",
239 | MainPath: "/root",
240 | SecondaryPath: "/somefile.txt",
241 | expected: false,
242 | },
243 | }
244 | for _, tc := range testCases {
245 | t.Run(tc.name, func(t *testing.T) {
246 | //create a temporary file
247 | if tc.expected {
248 | err := os.MkdirAll(tc.MainPath+tc.SecondaryPath, 0777)
249 | if err != nil {
250 | t.Fatal(err)
251 | }
252 | } else {
253 | os.RemoveAll(tc.MainPath + tc.SecondaryPath)
254 | os.RemoveAll(tc.MainPath)
255 | }
256 |
257 | var exists bool = Exists(tc.MainPath + tc.SecondaryPath)
258 | if exists != tc.expected {
259 | t.Errorf("Exists(%q) = %v; expected %v", tc.MainPath+tc.SecondaryPath, exists, tc.expected)
260 | } else if tc.expected {
261 | os.RemoveAll(tc.MainPath + tc.SecondaryPath)
262 | os.RemoveAll(tc.MainPath)
263 | }
264 | })
265 | }
266 | }
267 |
268 | func TestMergeINIFiles(t *testing.T) {
269 | testCases := []struct {
270 | name string
271 | inputPaths []string
272 | expectedData string
273 | }{
274 | {
275 | name: "Creds first and then configuration",
276 | inputPaths: []string{"../test/credentials", "../test/config"},
277 | expectedData: "[default]\naws_access_key_id = myKey\naws_secret_access_key = myAccessKey\n\n[account1]\naws_access_key_id = account1Key\naws_secret_access_key = account1AccessKey\n\n[account2]\naws_access_key_id = account2Key\naws_secret_access_key = account2AccessKey\n\n",
278 | },
279 | {
280 | name: "Configuration and then Creds",
281 | inputPaths: []string{"../test/config", "../test/credentials"},
282 | expectedData: "[default]\nregion = eu-west-1\n\n[profile account1]\nrole_arn = arn:aws:iam::123456789012:role/my-role\nsource_profile = default\n\n[profile account2]\nrole_arn = arn:aws:iam::125456389012:role/my-role\nsource_profile = default\n\n",
283 | },
284 | }
285 |
286 | for _, tc := range testCases {
287 | t.Run(tc.name, func(t *testing.T) {
288 | result, err := MergeINIFiles(tc.inputPaths)
289 | if err != nil {
290 | t.Fatalf("Error occurred: %v", err)
291 | }
292 | var actualDataBuf bytes.Buffer
293 | if _, err := actualDataBuf.ReadFrom(result); err != nil {
294 | t.Fatalf("Error reading merged data: %v", err)
295 | }
296 | actualData := actualDataBuf.String()
297 | if actualData != tc.expectedData {
298 | t.Errorf("Test case '%s' failed.\nExpected:\n%s\nGot:\n%s", tc.name, tc.expectedData, actualData)
299 | }
300 | })
301 | }
302 | }
303 |
304 | func TestCheckIfConfigExist(t *testing.T) {
305 | testCases := []struct {
306 | name string
307 | inputString string
308 | newKey string
309 | expectedData bool
310 | }{
311 | {
312 | name: "section dose not exist",
313 | inputString: `[Section1]
314 | key1 = value1
315 | [Section2]
316 | key2 = value2
317 | `,
318 | newKey: "newAAccount3",
319 | expectedData: false,
320 | },
321 | {
322 | name: "section exist",
323 | inputString: `[Section1]
324 | key1 = value1
325 | [Section2]
326 | key2 = value2
327 | `,
328 | newKey: "section1",
329 | expectedData: true,
330 | },
331 | {
332 | name: "No file exist",
333 | inputString: ``,
334 | newKey: "section1",
335 | expectedData: false,
336 | },
337 | }
338 |
339 | for _, tc := range testCases {
340 | t.Run(tc.name, func(t *testing.T) {
341 | existingIni := bytes.NewBufferString(tc.inputString)
342 | testIfExist := checkIfConfigExist(tc.newKey, *existingIni)
343 | assert.True(t, testIfExist == tc.expectedData, "Unexpected result for "+tc.name)
344 | })
345 | }
346 | }
347 |
--------------------------------------------------------------------------------
/core/loggin.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/fatih/structs"
8 | "github.com/shiena/ansicolor"
9 | log "github.com/sirupsen/logrus"
10 | prefixed "github.com/x-cray/logrus-prefixed-formatter"
11 | )
12 |
13 | var Verbosity bool
14 | var ErrorLevel bool
15 |
16 | func (f *PlainFormatter) Format(entry *log.Entry) ([]byte, error) {
17 | return []byte(fmt.Sprintf("%s\n", entry.Message)), nil
18 | }
19 |
20 | func ToggleDebug() {
21 | formatter := new(prefixed.TextFormatter)
22 | // Set specific colors for prefix and timestamp
23 | formatter.SetColorScheme(&prefixed.ColorScheme{
24 | InfoLevelStyle: "green",
25 | WarnLevelStyle: "yellow",
26 | ErrorLevelStyle: "red",
27 | FatalLevelStyle: "red",
28 | PanicLevelStyle: "red",
29 | DebugLevelStyle: "blue",
30 | PrefixStyle: "cyan",
31 | TimestampStyle: "black+h",
32 | })
33 | formatter.DisableTimestamp = true
34 | formatter.DisableUppercase = true
35 | formatter.ForceColors = true
36 | formatter.ForceFormatting = true
37 | log.SetFormatter(formatter)
38 | log.SetOutput(ansicolor.NewAnsiColorWriter(os.Stdout))
39 | if Verbosity {
40 | log.Info("Debug logs enabled")
41 | log.SetLevel(log.TraceLevel)
42 | } else if ErrorLevel {
43 | log.SetLevel(log.ErrorLevel)
44 | } else {
45 | log.SetLevel(log.InfoLevel)
46 | }
47 | }
48 |
49 | func DebugWithInfo(input interface{}) map[string]interface{} {
50 | return structs.Map(input)
51 | }
52 |
--------------------------------------------------------------------------------
/core/struct.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | type PlainFormatter struct {
4 | }
5 |
--------------------------------------------------------------------------------
/core/utils.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/manifoldco/promptui"
7 | log "github.com/sirupsen/logrus"
8 | )
9 |
10 | // PromptUI output prompt ui
11 | func PromptUI(label string, name string) string {
12 | validate := func(input string) error {
13 | if len(input) < 1 {
14 | return errors.New("context name must have more than 1 characters")
15 | }
16 | return nil
17 | }
18 | prompt := promptui.Prompt{
19 | Label: label,
20 | Validate: validate,
21 | Default: name,
22 | }
23 | result, err := prompt.Run()
24 |
25 | if err != nil {
26 | log.Fatalf("Prompt failed %v\n", err)
27 | }
28 | return result
29 | }
30 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module k8f
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.24.1
6 |
7 | require (
8 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0
9 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0
10 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.2.0
11 | github.com/aws/aws-sdk-go-v2 v1.18.1
12 | github.com/aws/aws-sdk-go-v2/credentials v1.13.26
13 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.100.1
14 | github.com/aws/aws-sdk-go-v2/service/eks v1.27.14
15 | github.com/aws/aws-sdk-go-v2/service/sts v1.19.2
16 | github.com/fatih/structs v1.1.0
17 | github.com/hashicorp/go-version v1.6.0
18 | github.com/imdario/mergo v0.3.16
19 | github.com/manifoldco/promptui v0.9.0
20 | github.com/stretchr/testify v1.9.0
21 | github.com/x-cray/logrus-prefixed-formatter v0.5.2
22 | gopkg.in/yaml.v2 v2.4.0
23 | )
24 |
25 | require (
26 | cloud.google.com/go/compute v1.20.0 // indirect
27 | cloud.google.com/go/compute/metadata v0.2.3 // indirect
28 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 // indirect
29 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 // indirect
30 | github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
31 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4 // indirect
32 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34 // indirect
33 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28 // indirect
34 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35 // indirect
35 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28 // indirect
36 | github.com/aws/aws-sdk-go-v2/service/sso v1.12.12 // indirect
37 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12 // indirect
38 | github.com/aws/smithy-go v1.13.5 // indirect
39 | github.com/chzyer/readline v1.5.1 // indirect
40 | github.com/davecgh/go-spew v1.1.1 // indirect
41 | github.com/go-logr/logr v1.2.4 // indirect
42 | github.com/gogo/protobuf v1.3.2 // indirect
43 | github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
44 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
45 | github.com/golang/protobuf v1.5.3 // indirect
46 | github.com/google/gofuzz v1.2.0 // indirect
47 | github.com/google/s2a-go v0.1.4 // indirect
48 | github.com/google/uuid v1.6.0 // indirect
49 | github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
50 | github.com/googleapis/gax-go/v2 v2.11.0 // indirect
51 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
52 | github.com/json-iterator/go v1.1.12 // indirect
53 | github.com/kylelemons/godebug v1.1.0 // indirect
54 | github.com/mattn/go-colorable v0.1.13 // indirect
55 | github.com/mattn/go-isatty v0.0.19 // indirect
56 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
57 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
58 | github.com/modern-go/reflect2 v1.0.2 // indirect
59 | github.com/onsi/ginkgo v1.16.5 // indirect
60 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
61 | github.com/pmezard/go-difflib v1.0.0 // indirect
62 | github.com/spf13/pflag v1.0.5 // indirect
63 | go.opencensus.io v0.24.0 // indirect
64 | golang.org/x/crypto v0.35.0 // indirect
65 | golang.org/x/net v0.36.0 // indirect
66 | golang.org/x/oauth2 v0.9.0 // indirect
67 | golang.org/x/sys v0.30.0 // indirect
68 | golang.org/x/term v0.29.0 // indirect
69 | golang.org/x/text v0.22.0 // indirect
70 | golang.org/x/time v0.3.0 // indirect
71 | google.golang.org/appengine v1.6.7 // indirect
72 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
73 | google.golang.org/grpc v1.56.3 // indirect
74 | google.golang.org/protobuf v1.33.0 // indirect
75 | gopkg.in/inf.v0 v0.9.1 // indirect
76 | gopkg.in/yaml.v3 v3.0.1 // indirect
77 | k8s.io/apimachinery v0.27.3 // indirect
78 | k8s.io/klog/v2 v2.100.1 // indirect
79 | k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect
80 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
81 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
82 | sigs.k8s.io/yaml v1.3.0 // indirect
83 | )
84 |
85 | require (
86 | github.com/aws/aws-sdk-go-v2/config v1.18.27
87 | github.com/jmespath/go-jmespath v0.4.0 // indirect
88 | github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02
89 | github.com/sirupsen/logrus v1.9.3
90 | github.com/spf13/cobra v1.7.0
91 | google.golang.org/api v0.128.0
92 | gopkg.in/ini.v1 v1.67.0
93 | k8s.io/client-go v0.27.3
94 | )
95 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go/compute v1.20.0 h1:cUOcywWuowO9It2i1KX1lIb0HH7gLv6nENKuZGnlcSo=
4 | cloud.google.com/go/compute v1.20.0/go.mod h1:kn5BhC++qUWR/AM3Dn21myV7QbgqejW04cAOrtppaQI=
5 | cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
6 | cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
7 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=
8 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
9 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg=
10 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
11 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo=
12 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg=
13 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0 h1:figxyQZXzZQIcP3njhC68bYUiTw45J8/SsHaLW8Ax0M=
14 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0/go.mod h1:TmlMW4W5OvXOmOyKNnor8nlMMiO1ctIyzmHme/VHsrA=
15 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E=
16 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2/go.mod h1:FbdwsQ2EzwvXxOPcMFYO8ogEc9uMMIj3YkmCdXdAFmk=
17 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8=
18 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s=
19 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.2.0 h1:Pmy0+3ox1IC3sp6musv87BFPIdQbqyPFjn7I8I0o2Js=
20 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.2.0/go.mod h1:ThfyMjs6auYrWPnYJjI3H4H++oVPrz01pizpu8lfl3A=
21 | github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
22 | github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
23 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
24 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
25 | github.com/aws/aws-sdk-go-v2 v1.18.1 h1:+tefE750oAb7ZQGzla6bLkOwfcQCEtC5y2RqoqCeqKo=
26 | github.com/aws/aws-sdk-go-v2 v1.18.1/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
27 | github.com/aws/aws-sdk-go-v2/config v1.18.27 h1:Az9uLwmssTE6OGTpsFqOnaGpLnKDqNYOJzWuC6UAYzA=
28 | github.com/aws/aws-sdk-go-v2/config v1.18.27/go.mod h1:0My+YgmkGxeqjXZb5BYme5pc4drjTnM+x1GJ3zv42Nw=
29 | github.com/aws/aws-sdk-go-v2/credentials v1.13.26 h1:qmU+yhKmOCyujmuPY7tf5MxR/RKyZrOPO3V4DobiTUk=
30 | github.com/aws/aws-sdk-go-v2/credentials v1.13.26/go.mod h1:GoXt2YC8jHUBbA4jr+W3JiemnIbkXOfxSXcisUsZ3os=
31 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4 h1:LxK/bitrAr4lnh9LnIS6i7zWbCOdMsfzKFBI6LUCS0I=
32 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4/go.mod h1:E1hLXN/BL2e6YizK1zFlYd8vsfi2GTjbjBazinMmeaM=
33 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34 h1:A5UqQEmPaCFpedKouS4v+dHCTUo2sKqhoKO9U5kxyWo=
34 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34/go.mod h1:wZpTEecJe0Btj3IYnDx/VlUzor9wm3fJHyvLpQF0VwY=
35 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28 h1:srIVS45eQuewqz6fKKu6ZGXaq6FuFg5NzgQBAM6g8Y4=
36 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28/go.mod h1:7VRpKQQedkfIEXb4k52I7swUnZP0wohVajJMRn3vsUw=
37 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35 h1:LWA+3kDM8ly001vJ1X1waCuLJdtTl48gwkPKWy9sosI=
38 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35/go.mod h1:0Eg1YjxE0Bhn56lx+SHJwCzhW+2JGtizsrx+lCqrfm0=
39 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.100.1 h1:AsLoN1zlf+PJ5DRzoegd8k/Zk9f/fBCMKxrZ4sXSE5k=
40 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.100.1/go.mod h1:tIctCeX9IbzsUTKHt53SVEcgyfxV2ElxJeEB+QUbc4M=
41 | github.com/aws/aws-sdk-go-v2/service/eks v1.27.14 h1:47HQVuJXgwvuoc4AT3rVdm77H0qGFbFnsuE4PRT+xX0=
42 | github.com/aws/aws-sdk-go-v2/service/eks v1.27.14/go.mod h1:QxuWcm9rlLkW3aEV8tiDzqZewnNSNUZfnqJvo1Nv9A0=
43 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28 h1:bkRyG4a929RCnpVSTvLM2j/T4ls015ZhhYApbmYs15s=
44 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28/go.mod h1:jj7znCIg05jXlaGBlFMGP8+7UN3VtCkRBG2spnmRQkU=
45 | github.com/aws/aws-sdk-go-v2/service/sso v1.12.12 h1:nneMBM2p79PGWBQovYO/6Xnc2ryRMw3InnDJq1FHkSY=
46 | github.com/aws/aws-sdk-go-v2/service/sso v1.12.12/go.mod h1:HuCOxYsF21eKrerARYO6HapNeh9GBNq7fius2AcwodY=
47 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12 h1:2qTR7IFk7/0IN/adSFhYu9Xthr0zVFTgBrmPldILn80=
48 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12/go.mod h1:E4VrHCPzmVB/KFXtqBGKb3c8zpbNBgKe3fisDNLAW5w=
49 | github.com/aws/aws-sdk-go-v2/service/sts v1.19.2 h1:XFJ2Z6sNUUcAz9poj+245DMkrHE4h2j5I9/xD50RHfE=
50 | github.com/aws/aws-sdk-go-v2/service/sts v1.19.2/go.mod h1:dp0yLPsLBOi++WTxzCjA/oZqi6NPIhoR+uF7GeMU9eg=
51 | github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
52 | github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
53 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
54 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
55 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
56 | github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
57 | github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
58 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
59 | github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
60 | github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
61 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
62 | github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
63 | github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
64 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
65 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
66 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
67 | github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
68 | github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
69 | github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
70 | github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
71 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
72 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
73 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
74 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
75 | github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=
76 | github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
77 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
78 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
79 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
80 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
81 | github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
82 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
83 | github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
84 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
85 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
86 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
87 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
88 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
89 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
90 | github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
91 | github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
92 | github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
93 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
94 | github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8=
95 | github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
96 | github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
97 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
98 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
99 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
100 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
101 | github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
102 | github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
103 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
104 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
105 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
106 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
107 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
108 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
109 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
110 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
111 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
112 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
113 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
114 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
115 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
116 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
117 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
118 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
119 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
120 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
121 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
122 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
123 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
124 | github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
125 | github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
126 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
127 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
128 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
129 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
130 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
131 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
132 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
133 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
134 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
135 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
136 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
137 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
138 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
139 | github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
140 | github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
141 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
142 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
143 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
144 | github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM=
145 | github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w=
146 | github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4=
147 | github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI=
148 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
149 | github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
150 | github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
151 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
152 | github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
153 | github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
154 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
155 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
156 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
157 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
158 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
159 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
160 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
161 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
162 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
163 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
164 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
165 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
166 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
167 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
168 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
169 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
170 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
171 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
172 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
173 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
174 | github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
175 | github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
176 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
177 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
178 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
179 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
180 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
181 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
182 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
183 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
184 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
185 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
186 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
187 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
188 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
189 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
190 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
191 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
192 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
193 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
194 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
195 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
196 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
197 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
198 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
199 | github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E=
200 | github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ=
201 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
202 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
203 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
204 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
205 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
206 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
207 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
208 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
209 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
210 | github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02 h1:v9ezJDHA1XGxViAUSIoO/Id7Fl63u6d0YmsAm+/p2hs=
211 | github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02/go.mod h1:RF16/A3L0xSa0oSERcnhd8Pu3IXSDZSK2gmGIMsttFE=
212 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
213 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
214 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
215 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
216 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
217 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
218 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
219 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
220 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
221 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
222 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
223 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
224 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
225 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
226 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
227 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
228 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
229 | github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
230 | github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
231 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
232 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
233 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
234 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
235 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
236 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
237 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
238 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
239 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
240 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
241 | golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
242 | golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
243 | golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
244 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
245 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
246 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
247 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
248 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
249 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
250 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
251 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
252 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
253 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
254 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
255 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
256 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
257 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
258 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
259 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
260 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
261 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
262 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
263 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
264 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
265 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
266 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
267 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
268 | golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
269 | golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
270 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
271 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
272 | golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs=
273 | golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw=
274 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
275 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
276 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
277 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
278 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
279 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
280 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
281 | golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
282 | golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
283 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
284 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
285 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
286 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
287 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
288 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
289 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
290 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
291 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
292 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
293 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
294 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
295 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
296 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
297 | golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
298 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
299 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
300 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
301 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
302 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
303 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
304 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
305 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
306 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
307 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
308 | golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
309 | golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
310 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
311 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
312 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
313 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
314 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
315 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
316 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
317 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
318 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
319 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
320 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
321 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
322 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
323 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
324 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
325 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
326 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
327 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
328 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
329 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
330 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
331 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
332 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
333 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
334 | google.golang.org/api v0.128.0 h1:RjPESny5CnQRn9V6siglged+DZCgfu9l6mO9dkX9VOg=
335 | google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750=
336 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
337 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
338 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
339 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
340 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
341 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
342 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
343 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
344 | google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao=
345 | google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64=
346 | google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM=
347 | google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
348 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
349 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
350 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
351 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
352 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
353 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
354 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
355 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
356 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
357 | google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
358 | google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
359 | google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
360 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
361 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
362 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
363 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
364 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
365 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
366 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
367 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
368 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
369 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
370 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
371 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
372 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
373 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
374 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
375 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
376 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
377 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
378 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
379 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
380 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
381 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
382 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
383 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
384 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
385 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
386 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
387 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
388 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
389 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
390 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
391 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
392 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
393 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
394 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
395 | k8s.io/api v0.27.3 h1:yR6oQXXnUEBWEWcvPWS0jQL575KoAboQPfJAuKNrw5Y=
396 | k8s.io/api v0.27.3/go.mod h1:C4BNvZnQOF7JA/0Xed2S+aUyJSfTGkGFxLXz9MnpIpg=
397 | k8s.io/apimachinery v0.27.3 h1:Ubye8oBufD04l9QnNtW05idcOe9Z3GQN8+7PqmuVcUM=
398 | k8s.io/apimachinery v0.27.3/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E=
399 | k8s.io/client-go v0.27.3 h1:7dnEGHZEJld3lYwxvLl7WoehK6lAq7GvgjxpA3nv1E8=
400 | k8s.io/client-go v0.27.3/go.mod h1:2MBEKuTo6V1lbKy3z1euEGnhPfGZLKTS9tiJ2xodM48=
401 | k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
402 | k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
403 | k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg=
404 | k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg=
405 | k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU=
406 | k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
407 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
408 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
409 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
410 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
411 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
412 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
413 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2022 NAME HERE
3 |
4 | */
5 | // TODO: cli commands to be used with DD & Nagios (or other monitoring system)
6 | // TODO: add output flag for: json,csv,toTerminal,pdf
7 | // testing GPG
8 | package main
9 |
10 | import (
11 | "k8f/cmd"
12 | )
13 |
14 | func main() {
15 | cmd.Execute()
16 | }
17 |
--------------------------------------------------------------------------------
/port.yml:
--------------------------------------------------------------------------------
1 | - identifier: AdamRussak_k8f_AYi6D0uVa-POq_Tc4jQY
2 | title: k8f
3 | team: []
4 | icon: DefaultBlueprint
5 | blueprint: sonarQubeProject
6 | properties:
7 | organization: null
8 | link: https://sonarqube.mylab.geekgalaxy.com/dashboard?id=AdamRussak_k8f_AYi6D0uVa-POq_Tc4jQY
9 | lastAnalysisDate: '2025-03-13T06:52:01.299Z'
10 | numberOfBugs: 0
11 | numberOfCodeSmells: 0
12 | numberOfVulnerabilities: 0
13 | numberOfHotSpots: 0
14 | numberOfDuplications: 0
15 | coverage: 0
16 | mainBranch: main
17 | tags: []
18 | revision: string
19 | managed: true
20 | qualityGateStatus: OK
21 |
--------------------------------------------------------------------------------
/provider/aws.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "errors"
7 | "k8f/core"
8 | "regexp"
9 | "strings"
10 |
11 | "github.com/aws/aws-sdk-go-v2/aws"
12 | "github.com/aws/aws-sdk-go-v2/config"
13 | "github.com/aws/aws-sdk-go-v2/credentials/stscreds"
14 | "github.com/aws/aws-sdk-go-v2/service/ec2"
15 | "github.com/aws/aws-sdk-go-v2/service/eks"
16 | "github.com/aws/aws-sdk-go-v2/service/eks/types"
17 | "github.com/aws/aws-sdk-go-v2/service/sts"
18 | log "github.com/sirupsen/logrus"
19 | "gopkg.in/ini.v1"
20 | )
21 |
22 | // to check if the profile is valid or not we need to load the []AwsProfiles and check if the profile is a Assume role or credentials, and do a simple call to aws to validate it works
23 | func validateCredentials(creds []AwsProfiles) (string, error) {
24 | log.Info("Validating AWS Credentials")
25 | for _, profile := range creds {
26 | var svc *sts.Client
27 | var conf aws.Config
28 | var err error
29 | conf, err = config.LoadDefaultConfig(context.TODO(), config.WithSharedConfigProfile(profile.Name))
30 | if err != nil {
31 | return profile.Name, err
32 | }
33 | if profile.IsRole {
34 | conf.Credentials = stsAssumeRole(profile)
35 | svc = sts.NewFromConfig(conf)
36 | } else {
37 | svc = sts.NewFromConfig(conf)
38 | }
39 | var input *sts.GetCallerIdentityInput = &sts.GetCallerIdentityInput{}
40 | _, err = svc.GetCallerIdentity(context.Background(), input)
41 | if err != nil {
42 | return profile.Name, err
43 | }
44 | }
45 | log.Info("AWS Credentials Validated")
46 | return "", nil
47 | }
48 |
49 | // https://docs.aws.amazon.com/sdk-for-go/api/aws/credentials/stscreds/#:~:text=or%20service%20clients.-,Assume%20Role,-To%20assume%20an
50 | func (c CommandOptions) FullAwsList() Provider {
51 | var f []Account
52 | core.CheckEnvVarOrSitIt("AWS_REGION", c.AwsRegion)
53 | profiles := c.GetLocalAwsProfiles()
54 | if len(profiles) == 0 {
55 | core.FailOnError(errors.New("no profiles to run with"), "process failed with Error")
56 | }
57 | addOnVersion := profiles[0].getVersion()
58 | l := getLatestEKS(getEKSversionsList(addOnVersion))
59 | log.Trace(profiles)
60 | c0 := make(chan Account)
61 | for _, profile := range profiles {
62 | go func(c0 chan Account, profile AwsProfiles, l string) {
63 | var re []Cluster
64 | log.Info(string("Using AWS profile: " + profile.Name))
65 | regions := profile.listRegions()
66 | c2 := make(chan []Cluster)
67 | for _, reg := range regions {
68 | go printOutResult(reg, l, profile, addOnVersion, c2)
69 | }
70 | for i := 0; i < len(regions); i++ {
71 | aRegion := <-c2
72 | if len(aRegion) > 0 {
73 | re = append(re, aRegion...)
74 | }
75 | }
76 | c0 <- Account{profile.Name, re, len(re), ""}
77 | }(c0, profile, l)
78 | }
79 | for i := 0; i < len(profiles); i++ {
80 | res := <-c0
81 | if len(res.Clusters) != 0 {
82 | f = append(f, res)
83 | }
84 | }
85 | return Provider{"aws", f, countTotal(f)}
86 | }
87 |
88 | // get Addons Supported EKS versions
89 | func (p AwsProfiles) getVersion() *eks.DescribeAddonVersionsOutput {
90 | var svc *eks.Client
91 | conf, err := config.LoadDefaultConfig(context.TODO(), config.WithSharedConfigProfile(p.ConfProfile))
92 | core.FailOnError(err, "Failed to get Version")
93 | if p.IsRole {
94 | conf.Credentials = stsAssumeRole(p)
95 | svc = eks.NewFromConfig(conf)
96 | } else {
97 | svc = eks.NewFromConfig(conf)
98 | }
99 | input2 := &eks.DescribeAddonVersionsInput{}
100 | r, err := svc.DescribeAddonVersions(context.TODO(), input2)
101 | core.FailOnError(err, "Failed to get Describe Version with profile: "+p.ConfProfile)
102 | return r
103 | }
104 |
105 | // gets the latest form suppported Addons
106 | func getLatestEKS(addons []string) string {
107 | return evaluateVersion(addons)
108 | }
109 |
110 | // create Version list
111 | func getEKSversionsList(addons *eks.DescribeAddonVersionsOutput) []string {
112 | var supportList []string
113 | for _, a := range addons.Addons {
114 | for _, c := range a.AddonVersions {
115 | for _, v := range c.Compatibilities {
116 | if !core.IfXinY(*v.ClusterVersion, supportList) {
117 | supportList = append(supportList, *v.ClusterVersion)
118 | }
119 | }
120 | }
121 | }
122 | return supportList
123 | }
124 |
125 | // get installed Version on existing Clusters
126 | func (p AwsProfiles) getEksCurrentVersion(cluster string, profile AwsProfiles, reg string, c3 chan []string) {
127 | var svc *eks.Client
128 | var conf aws.Config
129 | var err error
130 | conf, err = config.LoadDefaultConfig(context.TODO(), config.WithSharedConfigProfile(profile.ConfProfile))
131 | core.FailOnError(err, awsErrorMessage)
132 | if p.IsRole {
133 | conf.Credentials = stsAssumeRole(p)
134 | svc = eks.NewFromConfig(conf, func(o *eks.Options) {
135 | o.Region = reg
136 | })
137 | } else {
138 | svc = eks.NewFromConfig(conf, func(o *eks.Options) {
139 | o.Region = reg
140 | })
141 | }
142 | input := &eks.DescribeClusterInput{
143 | Name: aws.String(cluster),
144 | }
145 | result, err := svc.DescribeCluster(context.TODO(), input)
146 | core.FailOnError(err, "Failed to Get Cluster Info")
147 | c3 <- []string{cluster, *result.Cluster.Version}
148 | }
149 |
150 | // get all Regions avilable
151 | func (p AwsProfiles) listRegions() []string {
152 | core.CheckEnvVarOrSitIt("AWS_REGION", Kregion)
153 | var reg []string
154 | var svc *ec2.Client
155 | var conf aws.Config
156 | var err error
157 | conf, err = config.LoadDefaultConfig(context.TODO(), config.WithSharedConfigProfile(p.Name))
158 | core.FailOnError(err, awsErrorMessage)
159 | if p.IsRole {
160 | conf.Credentials = stsAssumeRole(p)
161 | svc = ec2.NewFromConfig(conf)
162 | } else {
163 | svc = ec2.NewFromConfig(conf)
164 | }
165 | input := &ec2.DescribeRegionsInput{}
166 | result, err := svc.DescribeRegions(context.TODO(), input)
167 | log.Debugf("Using profile: %s, ARN: %s, IsRole:%t", p.Name, p.Arn, p.IsRole)
168 | core.FailOnError(err, "Failed Get Region info")
169 | for _, r := range result.Regions {
170 | reg = append(reg, *r.RegionName)
171 | }
172 | return reg
173 | }
174 |
175 | func printOutResult(reg string, latest string, profile AwsProfiles, addons *eks.DescribeAddonVersionsOutput, c chan []Cluster) {
176 | var loc []Cluster
177 | var svc *eks.Client
178 | var conf aws.Config
179 | var err error
180 | conf, err = config.LoadDefaultConfig(context.TODO(), config.WithSharedConfigProfile(profile.ConfProfile))
181 | core.FailOnError(err, awsErrorMessage)
182 | if profile.IsRole {
183 | conf.Credentials = stsAssumeRole(profile)
184 | svc = eks.NewFromConfig(conf, func(o *eks.Options) {
185 | o.Region = reg
186 | })
187 | } else {
188 | svc = eks.NewFromConfig(conf, func(o *eks.Options) {
189 | o.Region = reg
190 | })
191 | }
192 | input := &eks.ListClustersInput{}
193 | result, err := svc.ListClusters(context.TODO(), input)
194 | core.FailOnError(err, "Failed to list Clusters")
195 | log.Debug(string("We are In Region: " + reg + " Profile " + profile.Name))
196 | if len(result.Clusters) > 0 {
197 | c3 := make(chan []string)
198 | for _, element := range result.Clusters {
199 | go profile.getEksCurrentVersion(element, profile, reg, c3)
200 | }
201 | for i := 0; i < len(result.Clusters); i++ {
202 | res := <-c3
203 | loc = append(loc, Cluster{res[0], res[1], latest, reg, "", "", HowManyVersionsBack(getEKSversionsList(addons), res[1])})
204 | }
205 | }
206 | c <- loc
207 | }
208 |
209 | func (c CommandOptions) GetLocalAwsProfiles() []AwsProfiles {
210 | var arr []AwsProfiles
211 | mergeconf, err := core.MergeINIFiles([]string{config.DefaultSharedConfigFilename(), config.DefaultSharedCredentialsFilename()})
212 | core.FailOnError(err, "failed to merge INI")
213 | creds, err := ini.Load(mergeconf)
214 | core.FailOnError(err, "Failed to load profile from creds")
215 | for _, p := range creds.Sections() {
216 | if len(p.Keys()) != 0 {
217 | profileName := removeString("profile", p.Name())
218 | _, isInArray := XinAwsProfiles(profileName, arr)
219 | kbool, karn := checkIfItsAssumeRole(p.Keys())
220 | if kbool && !isInArray {
221 | arr = append(arr, AwsProfiles{Name: profileName, IsRole: true, Arn: karn, ConfProfile: p.Name()})
222 | } else {
223 | arr = append(arr, AwsProfiles{Name: profileName, IsRole: false, ConfProfile: p.Name()})
224 | }
225 | }
226 | }
227 | // add a if statement to check if to fail on validation error or not
228 | fultyProfile, err := validateCredentials(arr)
229 | arr = c.finalizeValidation(arr, fultyProfile, err)
230 | kJson, _ := json.Marshal(arr)
231 | log.Debugf("profile in use: %s", string(kJson))
232 | return (arr) // Create JSON string response
233 | }
234 |
235 | func (c CommandOptions) finalizeValidation(arr []AwsProfiles, fultyProfile string, err error) []AwsProfiles {
236 | if c.Validate {
237 | core.FailOnError(err, credentialsValidationError+fultyProfile)
238 | } else {
239 | if err != nil {
240 | log.Error(credentialsValidationError + fultyProfile)
241 | for i, v := range arr {
242 | if v.Name == fultyProfile {
243 | arr = append(arr[:i], arr[i+1:]...)
244 | }
245 | }
246 | log.Warn(fultyProfile + " was removed from the list of profiles")
247 | }
248 | }
249 | return arr
250 | }
251 |
252 | // Connect Logic
253 | func (c CommandOptions) ConnectAllEks() AllConfig {
254 | var auth []Users
255 | var contexts []Contexts
256 | var clusters []Clusters
257 | var arnContext string
258 | var distinctArnContexts []string
259 | core.CheckEnvVarOrSitIt("AWS_REGION", c.AwsRegion)
260 | p := c.FullAwsList()
261 | awsProfiles := c.GetLocalAwsProfiles()
262 | for _, a := range p.Accounts {
263 | r := make(chan LocalConfig)
264 | for _, clus := range a.Clusters {
265 | go func(r chan LocalConfig, clus Cluster, a Account, commandOptions CommandOptions, awsProfiles []AwsProfiles) {
266 | var eksSvc *eks.Client
267 | var conf aws.Config
268 | var err error
269 | inProfile, _ := XinAwsProfiles(a.Name, awsProfiles)
270 | log.Infof("The Profile used is %s, and region is %s", awsProfiles[inProfile].Name, clus.Region)
271 | conf, err = config.LoadDefaultConfig(context.TODO(), config.WithSharedConfigProfile(awsProfiles[inProfile].Name))
272 | core.FailOnError(err, awsErrorMessage)
273 | if awsProfiles[inProfile].IsRole {
274 | conf.Credentials = stsAssumeRole(awsProfiles[inProfile])
275 | eksSvc = eks.NewFromConfig(conf, func(o *eks.Options) {
276 | o.Region = clus.Region
277 | })
278 | } else {
279 | eksSvc = eks.NewFromConfig(conf, func(o *eks.Options) {
280 | o.Region = clus.Region
281 | })
282 | }
283 | input := &eks.DescribeClusterInput{
284 | Name: aws.String(clus.Name),
285 | }
286 | result, err := eksSvc.DescribeCluster(context.TODO(), input)
287 | log.Debugf("the Profile that is used is: %s", awsProfiles[inProfile].Name)
288 | core.FailOnError(err, "Error calling DescribeCluster")
289 | r <- GenerateKubeConfiguration(result.Cluster, clus.Region, a, commandOptions)
290 | }(r, clus, a, c, awsProfiles)
291 | }
292 | for i := 0; i < len(a.Clusters); i++ {
293 | result := <-r
294 | duplicationFound := false
295 | // below check ensures we do not let duplicate clusters/contexts/users
296 | // get into the final configuration in case if there are different
297 | // aws profiles configured to assume the same role, because any kind of
298 | // a duplicate (cluster, context, user) makes kubectl crash
299 | for _, s := range distinctArnContexts {
300 | if s == result.Context.Cluster {
301 | duplicationFound = true
302 | break
303 | }
304 | }
305 | if duplicationFound {
306 | continue
307 | }
308 | arnContext = result.Context.Cluster
309 | distinctArnContexts = append(distinctArnContexts, result.Context.Cluster)
310 | auth = append(auth, Users{Name: arnContext, User: result.Authinfo})
311 | contexts = append(contexts, Contexts{Name: arnContext, Context: result.Context})
312 | clusters = append(clusters, Clusters{Name: arnContext, Cluster: result.Cluster})
313 | }
314 | }
315 | if c.Combined {
316 | log.Println("Started aws only config creation")
317 | c.CombineConfigs(AllConfig{auth, contexts, clusters}, arnContext)
318 | return AllConfig{}
319 | }
320 | log.Println("Started aws combined config creation")
321 | return AllConfig{auth, contexts, clusters}
322 | }
323 |
324 | // Create AWS Config
325 | func GenerateKubeConfiguration(cluster *types.Cluster, r string, a Account, c CommandOptions) LocalConfig {
326 | clusterName := c.SetClusterName(cluster.Arn)
327 | clusters := CCluster{
328 | Server: *cluster.Endpoint,
329 | CertificateAuthorityData: *cluster.CertificateAuthority.Data,
330 | }
331 | contexts := Context{
332 | Cluster: clusterName,
333 | User: clusterName,
334 | }
335 |
336 | authinfos := User{
337 | Exec: Exec{
338 | APIVersion: "client.authentication.k8s.io/v1beta1",
339 | Args: c.AwsArgs(r, *cluster.Name, *cluster.Arn),
340 | Env: c.AwsEnvs(a.Name),
341 | Command: c.setCommand(),
342 | },
343 | }
344 | return LocalConfig{authinfos, contexts, clusters}
345 | }
346 |
347 | func (c CommandOptions) SetClusterName(arn *string) string {
348 | split := strings.Split(*arn, ":")
349 | clusterName := strings.TrimPrefix(split[5], "cluster/")
350 | const breaker = ":"
351 | switch c.AwsClusterName {
352 | case false:
353 | outputName := split[4] + breaker + split[3] + breaker + clusterName
354 | return outputName
355 | case true:
356 | outputName := split[3] + breaker + clusterName
357 | return outputName
358 | default:
359 | return *arn
360 | }
361 | }
362 |
363 | func (c CommandOptions) setCommand() string {
364 | if c.AwsAuth {
365 | return "aws-iam-authenticator"
366 | }
367 | return "aws"
368 | }
369 |
370 | func (c CommandOptions) AwsArgs(region string, clusterName string, arn string) []string {
371 | var args []string
372 | if c.AwsRoleString != "" && !c.AwsAuth {
373 | args = []string{"--region", region, "eks", "get-token", "--cluster-name", clusterName, "--role-arn", "arn:aws:iam::" + SplitAzIDAndGiveItem(arn, ":", 4) + ":role/" + c.AwsRoleString}
374 | } else if c.AwsRoleString != "" && c.AwsAuth {
375 | args = []string{"token", "-i", clusterName, "--role-arn", "arn:aws:iam::" + SplitAzIDAndGiveItem(arn, ":", 4) + ":role/" + c.AwsRoleString}
376 | } else {
377 | args = []string{"--region", region, "eks", "get-token", "--cluster-name", clusterName}
378 | }
379 | return args
380 | }
381 |
382 | func (c CommandOptions) AwsEnvs(profile string) interface{} {
383 | if c.AwsEnvProfile {
384 | var envArray []Env
385 | envArray = append(envArray, Env{Name: "AWS_PROFILE", Value: profile})
386 | return envArray
387 | }
388 | return nil
389 | }
390 |
391 | func (c CommandOptions) GetSingleAWSCluster(clusterToFind string) Cluster {
392 | log.Info("Starting AWS find cluster named: " + clusterToFind)
393 | core.CheckEnvVarOrSitIt("AWS_REGION", c.AwsRegion)
394 | //get Profiles//search this name in account
395 | var f Cluster
396 | profiles := c.GetLocalAwsProfiles()
397 | c0 := make(chan Cluster)
398 | // search each profile
399 | for _, profile := range profiles {
400 | go c.getAwsClusters(c0, profile, clusterToFind)
401 | }
402 | for i := 0; i < len(profiles); i++ {
403 | res := <-c0
404 | if res.Name == clusterToFind {
405 | f = res
406 | }
407 | }
408 | return f
409 | //search this name in region
410 | //once it is found erturn info to the user
411 | }
412 |
413 | func (c CommandOptions) getAwsClusters(c0 chan Cluster, profile AwsProfiles, clusterToFind string) {
414 | var re Cluster
415 | log.Info(string("Using AWS profile: " + profile.Name))
416 | regions := profile.listRegions()
417 | profiles := c.GetLocalAwsProfiles()
418 | addOnVersion := profiles[0].getVersion()
419 | c2 := make(chan []Cluster)
420 | for _, reg := range regions {
421 | go printOutResult(reg, clusterToFind, profile, addOnVersion, c2)
422 | }
423 | for i := 0; i < len(regions); i++ {
424 | aRegion := <-c2
425 | if len(aRegion) > 0 {
426 | for _, cluster := range aRegion {
427 | if cluster.Name == clusterToFind {
428 | re = cluster
429 | }
430 | }
431 | }
432 | }
433 | c0 <- re
434 | }
435 |
436 | func checkIfItsAssumeRole(keys []*ini.Key) (bool, string) {
437 | var ARNRegexp = regexp.MustCompile(`^arn:(\w|-)*:iam::\d+:role\/?(\w+|-|\/|\.)*$`)
438 | for _, a := range keys {
439 | if ARNRegexp.MatchString(a.String()) {
440 | log.Debug("Is ARN: " + a.String())
441 | return true, a.String()
442 | }
443 | }
444 | return false, ""
445 | }
446 |
447 | func stsAssumeRole(awsProfile AwsProfiles) *aws.CredentialsCache {
448 | roleSession := "default"
449 | conf, err := config.LoadDefaultConfig(context.TODO(),
450 | config.WithRegion("us-east-1"),
451 | config.WithSharedConfigProfile(roleSession))
452 | core.FailOnError(err, awsErrorMessage)
453 | appCreds := stscreds.NewAssumeRoleProvider(sts.NewFromConfig(conf), awsProfile.Arn)
454 | creds := aws.NewCredentialsCache(appCreds)
455 | log.Debugf("Succsefully triggered stsAssumeRole for %s", awsProfile.Name)
456 | return creds
457 | }
458 |
459 | func XinAwsProfiles(x string, y []AwsProfiles) (int, bool) {
460 | for t := range y {
461 | if strings.Contains(y[t].ConfProfile, x) {
462 | log.Debugf("profile %s is the %x in list", x, t)
463 | return t, true
464 | }
465 | }
466 | return 0, false
467 | }
468 |
469 | func removeString(word, arn string) string {
470 | if !strings.Contains("default", arn) && strings.Contains(arn, word) {
471 | log.Debugf("Cutting %s from %s", word, arn)
472 | split := strings.Split(arn, " ")
473 | log.Debugf("length is %x and the profile name will be: %s", len(split), split[1])
474 | return split[1]
475 | } else if strings.Contains(word, arn) {
476 | return ""
477 | } else {
478 | return arn
479 | }
480 | }
481 |
--------------------------------------------------------------------------------
/provider/aws_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | const testErrorMessage = "Unexpected result for "
10 |
11 | func TestAwsArgs(t *testing.T) {
12 | const testRegion = "eu-west-1"
13 | const testRegionFlag = "--region"
14 | const testGetTokenFlag = "get-token"
15 | const testClusterArn = "arn:aws:eks:eu-west-1:123456789:cluster/testCluster"
16 | const testClusterNameFlag = "--cluster-name"
17 | const testClusterName = "testCluster"
18 | // the testcase should have the CommandOptions struct, the region string, the clustername string and the arn string and expected array
19 | testCases := []struct {
20 | name string
21 | Command CommandOptions
22 | region string
23 | clusterName string
24 | arn string
25 | expected []string
26 | }{
27 | {
28 | name: "Test_AwsArgs_only_role",
29 | Command: CommandOptions{AwsRoleString: "testRole", AwsAuth: false},
30 | region: testRegion,
31 | clusterName: testClusterName,
32 | arn: testClusterArn,
33 | expected: []string{testRegionFlag, testRegion, "eks", testGetTokenFlag, testClusterNameFlag, testClusterName, "--role-arn", "arn:aws:iam::123456789:role/testRole"},
34 | },
35 | {
36 | name: "Test_AwsArgs_only_role_with_aws_auth",
37 | Command: CommandOptions{AwsRoleString: "testRole", AwsAuth: true},
38 | region: testRegion,
39 | clusterName: testClusterName,
40 | arn: testClusterArn,
41 | expected: []string{"token", "-i", testClusterName, "--role-arn", "arn:aws:iam::123456789:role/testRole"},
42 | },
43 | {
44 | name: "Test_AwsArgs_without_role",
45 | Command: CommandOptions{AwsRoleString: "", AwsAuth: false},
46 | region: testRegion,
47 | clusterName: testClusterName,
48 | arn: testClusterArn,
49 | expected: []string{testRegionFlag, testRegion, "eks", testGetTokenFlag, testClusterNameFlag, testClusterName},
50 | },
51 | {
52 | name: "Test_AwsArgs_without_role_with_aws_auth",
53 | Command: CommandOptions{AwsRoleString: "", AwsAuth: true},
54 | region: testRegion,
55 | clusterName: testClusterName,
56 | arn: testClusterArn,
57 | expected: []string{testRegionFlag, testRegion, "eks", testGetTokenFlag, testClusterNameFlag, testClusterName},
58 | },
59 | }
60 | for _, tc := range testCases {
61 | t.Run(tc.name, func(t *testing.T) {
62 | var c CommandOptions = CommandOptions{AwsRoleString: tc.Command.AwsRoleString, AwsAuth: tc.Command.AwsAuth}
63 | args := c.AwsArgs(tc.region, tc.clusterName, tc.arn)
64 | assert.Equal(t, tc.expected, args, testErrorMessage+tc.name)
65 | })
66 | }
67 | }
68 |
69 | func TestSetClusterName(t *testing.T) {
70 | testCases := []struct {
71 | name string
72 | AwsClusterName bool
73 | ClusterName string
74 | expected string
75 | }{
76 | {
77 | name: "Full_Cluster_output",
78 | AwsClusterName: false,
79 | ClusterName: "arn:aws:eks:ap-southeast-2:234432434234:cluster/testCLuster01",
80 | expected: "234432434234:ap-southeast-2:testCLuster01",
81 | },
82 | {
83 | name: "Short_ARN_output",
84 | AwsClusterName: true,
85 | ClusterName: "arn:aws:eks:ap-southeast-2:234432434234:cluster/testCLuster02",
86 | expected: "ap-southeast-2:testCLuster02",
87 | },
88 | }
89 |
90 | for _, tc := range testCases {
91 | t.Run(tc.name, func(t *testing.T) {
92 | c := CommandOptions{AwsClusterName: tc.AwsClusterName}
93 | result := c.SetClusterName(&tc.ClusterName)
94 | assert.Equal(t, tc.expected, result, testErrorMessage+tc.name)
95 | })
96 | }
97 | }
98 |
99 | func TestRemoveString(t *testing.T) {
100 | testCases := []struct {
101 | name string
102 | fullString string
103 | whatToRemove string
104 | expected string
105 | }{
106 | {
107 | name: "Test_proifle_remove",
108 | fullString: "profile test",
109 | whatToRemove: "profile",
110 | expected: "test",
111 | }, {
112 | name: "Test_default_without_profile",
113 | fullString: "default",
114 | whatToRemove: "profile",
115 | expected: "default",
116 | },
117 | }
118 | for _, tc := range testCases {
119 | t.Run(tc.name, func(t *testing.T) {
120 | result := removeString(tc.whatToRemove, tc.fullString)
121 | assert.Equal(t, tc.expected, result, testErrorMessage+tc.name)
122 | })
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/provider/azure.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 | "k8f/core"
6 |
7 | "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
8 | "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice"
9 | "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions"
10 | log "github.com/sirupsen/logrus"
11 | "gopkg.in/yaml.v2"
12 | )
13 |
14 | var (
15 | ctx = context.Background()
16 | )
17 |
18 | func (c CommandOptions) FullAzureList() Provider {
19 | log.Info("Starting Azure Full List")
20 | var list []Account
21 | c0 := make(chan string)
22 | tenant := GetTenentList()
23 | for _, t := range tenant {
24 | log.Info("Start Tenanat: " + *t.DisplayName)
25 | go func(c0 chan string, t armsubscriptions.TenantIDDescription) {
26 | subs := listSubscriptions(*t.TenantID)
27 | c1 := make(chan Account)
28 | for _, s := range subs {
29 | log.Info("Start Subscription: " + s.Name)
30 | go getAllAKS(s, c1, *t.TenantID)
31 | }
32 | for i := 0; i < len(subs); i++ {
33 | res := <-c1
34 | list = append(list, res)
35 | log.Debug("Finished Subscription: " + subs[i].Name)
36 | }
37 | c0 <- "Finished Tenanat:"
38 | }(c0, t)
39 |
40 | }
41 | for i := 0; i < len(tenant); i++ {
42 | res := <-c0
43 | log.Debug(res + " " + *tenant[i].DisplayName)
44 | }
45 | return Provider{"azure", list, countTotal(list)}
46 | }
47 |
48 | func auth(tenantid string) *azidentity.AzureCLICredential {
49 | log.Debug("Start Authentication for tenant ID: " + tenantid)
50 | cred, err := azidentity.NewAzureCLICredential(&azidentity.AzureCLICredentialOptions{TenantID: tenantid})
51 | core.FailOnError(err, "Authentication Failed")
52 | log.Debug("Finished Authentication for tenant ID: " + tenantid)
53 | return cred
54 | }
55 |
56 | // get full list of tenants user got permissions to.
57 | func GetTenentList() []armsubscriptions.TenantIDDescription {
58 | log.Debug("Start getting tenant list")
59 | var res []armsubscriptions.TenantIDDescription
60 | tenants, err := armsubscriptions.NewTenantsClient(auth(""), nil)
61 | core.FailOnError(err, "Failed to get Tenants")
62 | tenant := tenants.NewListPager(nil)
63 | for tenant.More() {
64 | nextResult, err := tenant.NextPage(ctx)
65 | core.FailOnError(err, azureErrorMessage)
66 | for _, v := range nextResult.Value {
67 | res = append(res, *v)
68 | }
69 | }
70 | log.Debug("Finished getting tenant list")
71 | return res
72 | }
73 |
74 | func listSubscriptions(id string) []subs {
75 | log.Debug("Start getting Subscription list")
76 | var res []subs
77 | client, err := armsubscriptions.NewClient(auth(id), nil)
78 | core.FailOnError(err, "Failed to Auth")
79 | r := client.NewListPager(nil)
80 | for r.More() {
81 | nextResult, err := r.NextPage(ctx)
82 | core.FailOnError(err, azureErrorMessage)
83 | for _, v := range nextResult.Value {
84 | res = append(res, subs{*v.DisplayName, *v.SubscriptionID})
85 |
86 | }
87 | }
88 | log.Debug("Finished getting Subscription list")
89 | return res
90 | }
91 |
92 | // this is the only path we need to get the aks, now need to get latest version.
93 | func getAllAKS(subscription subs, c1 chan Account, id string) {
94 | var r []Cluster
95 | client, err := armcontainerservice.NewManagedClustersClient(subscription.Id, auth(id), nil)
96 | core.FailOnError(err, "failed to create client")
97 | pager := client.NewListPager(nil)
98 | for pager.More() {
99 | nextResult, err := pager.NextPage(ctx)
100 | core.FailOnError(err, azureErrorMessage)
101 | for _, v := range nextResult.Value {
102 | supportedAKS := findSupportedAksVersions(SplitAzIDAndGiveItem(*v.ID, "/", 4), *v.Name, subscription.Id, id)
103 | l := getAksConfig(supportedAKS)
104 | log.Debug("current Version is: " + *v.Properties.KubernetesVersion)
105 | r = append(r, Cluster{*v.Name, *v.Properties.KubernetesVersion, l, *v.Location, *v.ID, "", microsoftSupportedVersion(l, *v.Properties.KubernetesVersion)})
106 | }
107 | }
108 | c1 <- Account{subscription.Name, r, len(r), id}
109 | }
110 |
111 | // Getting AKS Config
112 | func getAksConfig(supportedList []string) string {
113 | return evaluateVersion(supportedList)
114 | }
115 |
116 | // Getting AKS Supported K8S versions
117 | func findSupportedAksVersions(resourceGroup string, resourceName string, subscription string, id string) []string {
118 | var supportList []string
119 | log.WithField("CommandOptions", log.Fields{"subscription": subscription, "tenantID": id, "resourceName": resourceName}).Debug("getAksConfig Variables and Values: ")
120 | client, err := armcontainerservice.NewManagedClustersClient(subscription, auth(id), nil)
121 | core.FailOnError(err, "Create Client Failed")
122 | profile, err := client.GetUpgradeProfile(ctx, resourceGroup, resourceName, nil)
123 | core.FailOnError(err, "Update Profile Failed")
124 | for _, a := range profile.Properties.ControlPlaneProfile.Upgrades {
125 | supportList = append(supportList, *a.KubernetesVersion)
126 | }
127 | log.Debug("List of Supported Versions")
128 | log.Debug(supportList)
129 | return supportList
130 | }
131 |
132 | // Getting AKS profile (contains most of the information needed for list and Connect commnands)
133 | func getAksProfile(client *armcontainerservice.ManagedClustersClient, resourceGroupName string, resourceName string) AllConfig {
134 | log.WithField("CommandOptions", log.Fields{"struct": core.DebugWithInfo(client), "resourceGroupName": resourceGroupName, "resourceName": resourceName}).Debug("getAksProfile Variables and Values: ")
135 | l, err := client.ListClusterUserCredentials(ctx, resourceGroupName, resourceName, nil)
136 | core.FailOnError(err, "get user creds Failed")
137 | y := Config{}
138 | for _, c := range l.Kubeconfigs {
139 | b64 := c.Value
140 | err := yaml.Unmarshal(b64, &y)
141 | core.FailOnError(err, "Failed To Unmarshal Config")
142 | }
143 | return AllConfig{auth: y.Users, context: y.Contexts, clusters: y.Clusters}
144 | }
145 |
146 | // create the KubeConfig info needed for AKS
147 | func (c CommandOptions) ConnectAllAks() AllConfig {
148 | var authe []Users
149 | var context []Contexts
150 | var clusters []Clusters
151 | var arnContext string
152 | p := c.FullAzureList()
153 | for _, a := range p.Accounts {
154 | chanel := make(chan AllConfig)
155 | for _, c := range a.Clusters {
156 | go func(chanel chan AllConfig, c Cluster, a Account) {
157 | log.WithField("Cluster Struct", log.Fields{"struct": core.DebugWithInfo(c), "tenentAuth": core.DebugWithInfo(a)}).Debug("Creating NewManagedClustersClient")
158 | client, err := armcontainerservice.NewManagedClustersClient(SplitAzIDAndGiveItem(c.Id, "/", 2), auth(a.Tenanat), nil)
159 | core.FailOnError(err, "get user creds Failed")
160 | chanel <- getAksProfile(client, SplitAzIDAndGiveItem(c.Id, "/", 4), c.Name)
161 | }(chanel, c, a)
162 | }
163 | for i := 0; i < len(a.Clusters); i++ {
164 | response := <-chanel
165 | arnContext = response.context[0].Context.User
166 | authe = append(authe, response.auth...)
167 | context = append(context, response.context...)
168 | clusters = append(clusters, response.clusters...)
169 | }
170 | }
171 | if c.Combined {
172 | log.Debug("Started azure only config creation")
173 | c.CombineConfigs(AllConfig{authe, context, clusters}, arnContext)
174 | return AllConfig{}
175 | } else {
176 | log.Debug("Started azure combined config creation")
177 | return AllConfig{authe, context, clusters}
178 | }
179 |
180 | }
181 |
182 | func (c CommandOptions) GetSingleAzureCluster(clusterToFind string) Cluster {
183 | log.Info("Starting Azure find cluster named: " + clusterToFind)
184 | c0 := make(chan Cluster)
185 | tenant := GetTenentList()
186 | for _, t := range tenant {
187 | log.Info("Start Tenanat: " + *t.DisplayName)
188 | go getAzureClusters(c0, t, clusterToFind)
189 | }
190 | for i := 0; i < len(tenant); i++ {
191 | res := <-c0
192 | if res.Name == clusterToFind {
193 | return res
194 | }
195 | }
196 | return Cluster{}
197 | }
198 | func getAzureClusters(c0 chan Cluster, t armsubscriptions.TenantIDDescription, clusterToFind string) {
199 | var list Cluster
200 | subs := listSubscriptions(*t.TenantID)
201 | c1 := make(chan Account)
202 | for _, s := range subs {
203 | log.Info("Start Subscription: " + s.Name)
204 | go getAllAKS(s, c1, *t.TenantID)
205 | }
206 | for i := 0; i < len(subs); i++ {
207 | res := <-c1
208 | for a := range res.Clusters {
209 | if res.Clusters[a].Name == clusterToFind {
210 | list = res.Clusters[a]
211 | break
212 | }
213 | }
214 | log.Debug("Finished Subscription: " + subs[i].Name)
215 | }
216 | c0 <- list
217 | }
218 |
--------------------------------------------------------------------------------
/provider/constant.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | const Kregion = "eu-west-1"
4 | const awsErrorMessage = "Failed to create new session"
5 | const azureErrorMessage = "failed to advance page"
6 | const decodeError = "failed to decode base64"
7 | const backupExtnesion = ".bak"
8 | const filedtoCopyToTarget = "failed to Copy target file"
9 | const credentialsValidationError = "credentials validation error for profile "
10 |
--------------------------------------------------------------------------------
/provider/core.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io"
7 | "k8f/core"
8 | "os"
9 | "path/filepath"
10 | "reflect"
11 | "strconv"
12 | "strings"
13 |
14 | "github.com/hashicorp/go-version"
15 | log "github.com/sirupsen/logrus"
16 | "gopkg.in/yaml.v2"
17 | )
18 |
19 | func returnMinorDiff(splitcurernt, splitLatest []string) int {
20 | latestMinor, err := strconv.Atoi(splitLatest[1])
21 | core.FailOnError(err, "faild to convert string to int")
22 | currentMinor, err := strconv.Atoi(splitcurernt[1])
23 | core.FailOnError(err, "faild to convert string to int")
24 | getStatus := latestMinor - currentMinor
25 | return getStatus
26 | }
27 |
28 | // evaluate latest version from addon version list
29 | func evaluateVersion(list []string) string {
30 | var latest string
31 | for _, v := range list {
32 | var lt *version.Version
33 | var err error
34 | v1, err := version.NewVersion(v)
35 | core.FailOnError(err, "Error Evaluating Version")
36 | if latest == "" {
37 | lt, err = version.NewVersion("0.0")
38 | } else {
39 | lt, err = version.NewVersion(latest)
40 | }
41 | core.FailOnError(err, "Error Evaluating Version")
42 | // Options availabe
43 | if v1.GreaterThan(lt) {
44 | latest = v
45 | } // GreaterThen
46 | }
47 | return latest
48 | }
49 |
50 | // Microsoft Compliance
51 | func microsoftSupportedVersion(latest string, current string) string {
52 | //IMPORTANT: only supports same major at the moment!!!
53 | splitLatest := strings.Split(latest, ".")
54 | splitcurernt := strings.Split(current, ".")
55 | // make sure its the same major
56 | if splitLatest[0] == splitcurernt[0] {
57 | getStatus := returnMinorDiff(splitcurernt, splitLatest)
58 | //if its latest minor or -1, mark as ok
59 | if getStatus <= 1 {
60 | return "OK"
61 | //if its minor -2 show warning
62 | } else if getStatus > 1 && getStatus <= 2 {
63 | return "Warning"
64 | // if its minor > -2 show Critical
65 | } else {
66 | return "Critical"
67 | }
68 |
69 | }
70 | return "Unknown"
71 | }
72 |
73 | // provide version compare
74 | func HowManyVersionsBack(versionsList []string, currentVersion string) string {
75 | log.Debug("current version is: " + currentVersion)
76 | latest := evaluateVersion(versionsList)
77 | splitcurernt := strings.Split(currentVersion, ".")
78 | splitLatest := strings.Split(latest, ".")
79 | //check if same major
80 | if splitLatest[0] == splitcurernt[0] {
81 | getStatus := returnMinorDiff(splitcurernt, splitLatest)
82 | if getStatus <= 1 {
83 | return "Perfect"
84 | } else if getStatus <= 3 {
85 | return "OK"
86 | } else {
87 | return "Warning"
88 | }
89 | }
90 | return "Critical"
91 | }
92 |
93 | func (c CommandOptions) getYamlOrJsonOutput(p interface{}) ([]byte, error) {
94 | var kJson []byte
95 | var err error
96 | log.Debug("start RunResult Func")
97 | if c.Output == "json" {
98 | log.Info("start Json Marshal")
99 | kJson, _ = json.Marshal(p)
100 | } else if c.Output == "yaml" {
101 | log.Info("start YAML Marshal")
102 | kJson, _ = yaml.Marshal(p)
103 | } else {
104 | err = fmt.Errorf("requested Output is not supported")
105 | }
106 | log.Info("returning Output Marshal")
107 | return kJson, err
108 | }
109 |
110 | // printout format selection
111 | func (c CommandOptions) PrintoutResults(p interface{}) (string, error) {
112 | data, err := c.getYamlOrJsonOutput(p)
113 | return string(data), err
114 | }
115 |
116 | // func to count ammount of Cluster in an account
117 | func countTotal(f []Account) int {
118 | var count int
119 | for _, a := range f {
120 | count = count + a.TotalCount
121 | }
122 | return count
123 | }
124 |
125 | // func to merge kubeconfig output to singe config file
126 | func (c CommandOptions) CombineConfigs(configs AllConfig, arn string) {
127 | var y []byte
128 | var err error
129 | clientConfig := Config{
130 | Kind: "Config",
131 | APIVersion: "v1",
132 | Clusters: configs.clusters,
133 | Contexts: configs.context,
134 | CurrentContext: arn,
135 | Preferences: Preferences{},
136 | Users: configs.auth,
137 | }
138 | if c.DryRun {
139 | log.Debugf("Dry-run will Output a %s Output", c.Output)
140 | output, err := c.PrintoutResults(clientConfig)
141 | core.FailOnError(err, "failed to printout results")
142 | fmt.Println(output)
143 | } else {
144 | if c.Backup {
145 | y, _ = yaml.Marshal(clientConfig)
146 | log.Debug("calling copy file to bak")
147 | c.Configcopy()
148 | }
149 | if c.Merge {
150 | y, err = c.runMerge(clientConfig)
151 | core.FailOnError(err, "failed to merge configs")
152 | c.cleanFile()
153 | } else {
154 | y, _ = yaml.Marshal(clientConfig)
155 | }
156 | err := os.WriteFile(c.Path, y, 0666)
157 | core.FailOnError(err, "failed to save config")
158 | log.Infof("「 %s 」 write successful!\n", c.Path)
159 | }
160 | }
161 |
162 | func (c CommandOptions) FullCloudConfig() {
163 | var auth []Users
164 | var context []Contexts
165 | var clusters []Clusters
166 | r := make(chan AllConfig)
167 | for _, cloud := range []string{"azure", "aws"} {
168 | go func(cloud string, r chan AllConfig, c CommandOptions) {
169 | var res AllConfig
170 | if cloud == "azure" {
171 | res = c.ConnectAllAks()
172 | } else if cloud == "aws" {
173 | res = c.ConnectAllEks()
174 | }
175 | r <- res
176 | }(cloud, r, c)
177 | }
178 | for i := 0; i < len([]string{"azure", "aws"}); i++ {
179 | response := <-r
180 | auth = append(auth, response.auth...)
181 | context = append(context, response.context...)
182 | clusters = append(clusters, response.clusters...)
183 | }
184 | c.CombineConfigs(AllConfig{auth: auth, context: context, clusters: clusters}, context[0].Context.User)
185 | }
186 |
187 | func (c CommandOptions) Configcopy() {
188 | sourceFileStat, err := os.Stat(c.Path)
189 | core.FailOnError(err, "Issue Findign the Files in the path: "+c.Path)
190 | if !sourceFileStat.Mode().IsRegular() {
191 | core.FailOnError(err, c.Path+" is not a regular file")
192 | }
193 | source, err := os.Open(c.Path)
194 | core.FailOnError(err, "failed to Open target file")
195 | defer source.Close()
196 | var destination *os.File
197 | backupVersion := c.GetBackupFileVersion()
198 | if backupVersion >= 1 {
199 | destination, err = os.Create(c.Path + backupExtnesion + "." + fmt.Sprint(backupVersion))
200 | log.Debug(c.Path + backupExtnesion + "." + fmt.Sprint(backupVersion))
201 | core.FailOnError(err, filedtoCopyToTarget)
202 | } else {
203 | destination, err = os.Create(c.Path + backupExtnesion)
204 | core.FailOnError(err, filedtoCopyToTarget)
205 | }
206 |
207 | defer destination.Close()
208 | _, err = io.Copy(destination, source)
209 | core.FailOnError(err, filedtoCopyToTarget)
210 | }
211 |
212 | func (c CommandOptions) GetBackupFileVersion() int {
213 | dir := filepath.Dir(c.Path)
214 | dirFiles, err := os.ReadDir(dir)
215 | core.FailOnError(err, "failed to list all files in directory")
216 | var countBackups []string
217 | for _, file := range dirFiles {
218 | if strings.Contains(file.Name(), backupExtnesion) {
219 | countBackups = append(countBackups, file.Name())
220 | log.Debug(file.Name(), " ", file.IsDir())
221 | }
222 | }
223 | return len(countBackups)
224 | }
225 | func SplitAzIDAndGiveItem(input string, seperator string, out int) string {
226 | s := strings.Split(input, seperator)
227 | log.Debug("Split output")
228 | return s[out]
229 | }
230 |
231 | func (c CommandOptions) cleanFile() {
232 | // Open the file with write only mode and set the file mode to 0644
233 | file, err := os.OpenFile(c.Path, os.O_WRONLY|os.O_TRUNC, 0644)
234 | if err != nil {
235 | panic(err)
236 | }
237 | defer file.Close()
238 |
239 | // Truncate the file content to 0
240 | if err := file.Truncate(0); err != nil {
241 | panic(err)
242 | }
243 |
244 | // Get the file info to print the file size
245 | fileStat, err := file.Stat()
246 | if err != nil {
247 | panic(err)
248 | }
249 |
250 | // Print the file size after cleaning the file
251 | log.Debug("File size after cleaning:", fileStat.Size())
252 | }
253 |
254 | func checkIfStructInit(u interface{}, key string) bool {
255 | v := reflect.ValueOf(u)
256 | t := v.Type()
257 |
258 | for i := 0; i < v.NumField(); i++ {
259 | field := v.Field(i)
260 | tag := t.Field(i).Tag.Get("yaml")
261 | if tag == key+",omitempty" {
262 | // Check if the field is set to its zero value
263 | if reflect.DeepEqual(field.Interface(), reflect.Zero(field.Type()).Interface()) {
264 | continue
265 | }
266 | // If any field has a non-zero value, return true
267 | return true
268 | }
269 | }
270 | return false
271 | }
272 |
273 | func (c CommandOptions) StructOutput(inpoutInfo interface{}) {
274 | data, err := c.getYamlOrJsonOutput(inpoutInfo)
275 | core.FailOnError(err, "failed to get struct as "+c.Output)
276 | err = os.WriteFile(c.Path+"."+c.Output, data, 0666)
277 | core.FailOnError(err, "failed to save config")
278 | }
279 |
--------------------------------------------------------------------------------
/provider/core_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io"
7 | "k8f/core"
8 | "os"
9 | "testing"
10 |
11 | log "github.com/sirupsen/logrus"
12 | "github.com/stretchr/testify/assert"
13 | "gopkg.in/yaml.v2"
14 | )
15 |
16 | func TestReturnMinorDiff(t *testing.T) {
17 | testCases := []struct {
18 | name string
19 | currentVersion []string
20 | latestVersion []string
21 | expected int
22 | }{
23 | {
24 | name: "PerfectMatch",
25 | currentVersion: []string{"1", "27"},
26 | latestVersion: []string{"1", "27"},
27 | expected: 0,
28 | }, {
29 | name: "PerfectMatch-1",
30 | currentVersion: []string{"1", "26"},
31 | latestVersion: []string{"1", "27"},
32 | expected: 1,
33 | }, {
34 | name: "WarningMatch",
35 | currentVersion: []string{"1", "22"},
36 | latestVersion: []string{"1", "27"},
37 | expected: 5,
38 | }, {
39 | name: "CriticalMatch",
40 | currentVersion: []string{"1", "0"},
41 | latestVersion: []string{"1", "27"},
42 | expected: 27,
43 | },
44 | }
45 | for _, tc := range testCases {
46 | t.Run(tc.name, func(t *testing.T) {
47 | // Test case: Perfect match, current version is the latest
48 | result := returnMinorDiff(tc.currentVersion, tc.latestVersion)
49 | assert.Equal(t, tc.expected, result, "Unexpected result for "+tc.name)
50 |
51 | })
52 | }
53 | }
54 | func TestEvaluateVersion(t *testing.T) {
55 | t.Run("EmptyList", func(t *testing.T) {
56 | // Test case: Empty list
57 | list := []string{}
58 | result := evaluateVersion(list)
59 | expected := ""
60 | assert.Equal(t, expected, result, "Unexpected result for empty list")
61 | })
62 |
63 | t.Run("SingleVersion", func(t *testing.T) {
64 | // Test case: Single version in the list
65 | list := []string{"1.2.3"}
66 | result := evaluateVersion(list)
67 | expected := "1.2.3"
68 | assert.Equal(t, expected, result, "Unexpected result for single version")
69 | })
70 |
71 | t.Run("MultipleVersions", func(t *testing.T) {
72 | // Test case: Multiple versions in the list
73 | list := []string{"1.0.0", "1.2.3", "2.0.0", "0.1.0"}
74 | result := evaluateVersion(list)
75 | expected := "2.0.0"
76 | assert.Equal(t, expected, result, "Unexpected result for multiple versions")
77 | })
78 | // Add more test cases for other scenarios
79 | }
80 |
81 | func TestMicrosoftSupportedVersion(t *testing.T) {
82 | setCurrent := "10.0.0"
83 | testCases := []struct {
84 | name string
85 | latest string
86 | current string
87 | expected string
88 | }{
89 | {
90 | name: "SameMajorVersion",
91 | latest: setCurrent,
92 | current: setCurrent,
93 | expected: "OK",
94 | },
95 | {
96 | name: "MinorVersionDifference",
97 | latest: "10.1.0",
98 | current: setCurrent,
99 | expected: "OK",
100 | },
101 | {
102 | name: "MinorVersionWarning",
103 | latest: "10.10.0",
104 | current: "10.8.0",
105 | expected: "Warning",
106 | },
107 | {
108 | name: "MinorVersionCritical",
109 | latest: "10.6.0",
110 | current: setCurrent,
111 | expected: "Critical",
112 | },
113 | {
114 | name: "DifferentMajorVersion",
115 | latest: "11.0.0",
116 | current: "10.1.0",
117 | expected: "Unknown",
118 | },
119 | // Add more test cases for other scenarios
120 | }
121 |
122 | for _, tc := range testCases {
123 | t.Run(tc.name, func(t *testing.T) {
124 | result := microsoftSupportedVersion(tc.latest, tc.current)
125 | assert.Equal(t, tc.expected, result, "Unexpected result for "+tc.name)
126 | })
127 | }
128 | }
129 |
130 | func TestHowManyVersionsBack(t *testing.T) {
131 | versionsList := []string{"1.24", "1.23", "1.22", "1.21", "1.20", "1.27", "1.26", "1.25"}
132 | testCases := []struct {
133 | name string
134 | currentVersion string
135 | expected string
136 | }{
137 | {
138 | name: "PerfectMatch",
139 | currentVersion: "1.27",
140 | expected: "Perfect",
141 | }, {
142 | name: "PerfectMatch-1",
143 | currentVersion: "1.26",
144 | expected: "Perfect",
145 | }, {
146 | name: "WarningMatch",
147 | currentVersion: "1.22",
148 | expected: "Warning",
149 | }, {
150 | name: "CriticalMatch",
151 | currentVersion: "0.5",
152 | expected: "Critical",
153 | },
154 | }
155 | for _, tc := range testCases {
156 | t.Run(tc.name, func(t *testing.T) {
157 | // Test case: Perfect match, current version is the latest
158 | result := HowManyVersionsBack(versionsList, tc.currentVersion)
159 | assert.Equal(t, tc.expected, result, "Unexpected result for "+tc.name)
160 |
161 | })
162 | }
163 | }
164 |
165 | func TestPrintoutResults(t *testing.T) {
166 | data := struct {
167 | Name string `json:"name"`
168 | Age int `json:"age"`
169 | }{
170 | Name: "John Doe",
171 | Age: 30,
172 | }
173 |
174 | t.Run("JSONOutput", func(t *testing.T) {
175 | // Test case: JSON output
176 | c := CommandOptions{Output: "json"}
177 | result, err := c.PrintoutResults(data)
178 | if err != nil {
179 | t.Errorf("Didnt expected an error, but got none.")
180 | }
181 | expected, _ := json.Marshal(data)
182 | assert.Equal(t, string(expected), result, "Unexpected result for JSON output")
183 | })
184 |
185 | t.Run("YAMLOutput", func(t *testing.T) {
186 | // Test case: YAML output
187 | c := CommandOptions{Output: "yaml"}
188 | result, err := c.PrintoutResults(data)
189 | if err != nil {
190 | t.Errorf("Didnt expected an error, but got none.")
191 | }
192 | expected, _ := yaml.Marshal(data)
193 | assert.Equal(t, string(expected), result, "Unexpected result for YAML output")
194 | })
195 |
196 | t.Run("UnsupportedOutput", func(t *testing.T) {
197 | // Test case: Unsupported output type
198 | c := CommandOptions{Output: "csv"}
199 | _, err := c.PrintoutResults(data)
200 | if assert.Error(t, err) {
201 | expectedError := "requested Output is not supported"
202 | assert.Equal(t, expectedError, err.Error())
203 | }
204 | // expected := "Requested Output is not supported"
205 | // assert.Equal(t, expected, result, "Unexpected result for unsupported output type")
206 | })
207 | // Add more test cases for other scenarios
208 | }
209 |
210 | func TestCountTotal(t *testing.T) {
211 | accounts := []Account{
212 | {TotalCount: 10},
213 | {TotalCount: 20},
214 | {TotalCount: 30},
215 | }
216 |
217 | t.Run("MultipleAccounts", func(t *testing.T) {
218 | // Test case: Multiple accounts
219 | result := countTotal(accounts)
220 | expected := 60
221 | assert.Equal(t, expected, result, "Unexpected total count for multiple accounts")
222 | })
223 |
224 | t.Run("EmptyAccounts", func(t *testing.T) {
225 | // Test case: Empty accounts
226 | result := countTotal([]Account{})
227 | expected := 0
228 | assert.Equal(t, expected, result, "Unexpected total count for empty accounts")
229 | })
230 | // Add more test cases for other scenarios
231 | }
232 |
233 | func TestCheckIfStructInit(t *testing.T) {
234 | t.Run("FieldExists", func(t *testing.T) {
235 | // Test case: Field exists in the struct
236 | user := User{
237 | Exec: Exec{APIVersion: "1", Args: []string{"1", "2"}, Command: "noting", Env: "dev", ProvideClusterInfo: true},
238 | ClientCertificateData: "certData",
239 | ClientKeyData: "clientKeyData",
240 | Token: "veryComplicatedToken",
241 | }
242 |
243 | result := checkIfStructInit(user, "exec")
244 | expected := true
245 | assert.Equal(t, expected, result, "Unexpected result for field existence")
246 | })
247 |
248 | t.Run("FieldOmitted", func(t *testing.T) {
249 | // Test case: Field is omitted in the struct
250 | user := User{}
251 |
252 | result := checkIfStructInit(user, "exec")
253 | expected := false
254 | assert.Equal(t, expected, result, "Unexpected result for field omission")
255 | })
256 | // Add more test cases for other scenarios
257 | }
258 |
259 | func TestCleanFile(t *testing.T) {
260 | // Create a temporary file for testing
261 | tmpfile, err := os.CreateTemp("", "testfile")
262 | if err != nil {
263 | t.Fatal(err)
264 | }
265 | defer os.Remove(tmpfile.Name())
266 | // Write some content to the file
267 | content := []byte("Test content")
268 | _, err = tmpfile.Write(content)
269 | if err != nil {
270 | t.Fatal(err)
271 | }
272 | // Close the file before cleaning it
273 | tmpfile.Close()
274 |
275 | // Create a CommandOptions instance with the temporary file path
276 | c := CommandOptions{Path: tmpfile.Name()}
277 |
278 | // Call the cleanFile method
279 | c.cleanFile()
280 | // Open the file again to check if it's empty
281 | file, err := os.Open(tmpfile.Name())
282 | if err != nil {
283 | t.Fatal(err)
284 | }
285 | defer file.Close()
286 | // Read the file content
287 | _, err = io.ReadAll(file)
288 | if err != nil {
289 | t.Fatal(err)
290 | }
291 | // Check if the file is empty
292 | fileStat, err := file.Stat()
293 | if err != nil {
294 | t.Fatal(err)
295 | }
296 | // Verify that the file size is 0 after cleaning
297 | if fileStat.Size() != 0 {
298 | t.Errorf("Expected file size after cleaning: 0, got: %d", fileStat.Size())
299 | }
300 | }
301 |
302 | func TestConfigCopy(t *testing.T) {
303 | t.Run("RegularFile", func(t *testing.T) {
304 | // Create a temporary file for testing
305 | tmpfile, err := os.CreateTemp("", "testfile")
306 | if err != nil {
307 | t.Fatal(err)
308 | }
309 | defer os.RemoveAll(tmpfile.Name())
310 |
311 | // Create a CommandOptions instance with the temporary file path
312 | c := CommandOptions{Path: tmpfile.Name()}
313 |
314 | // Call the Configcopy method
315 | c.Configcopy()
316 |
317 | // Check if the backup file exists
318 | _, err = os.Stat(tmpfile.Name() + ".bak")
319 | if err != nil {
320 | t.Errorf("Expected backup file to exist, got error: %v", err)
321 | }
322 | os.RemoveAll(tmpfile.Name() + ".bak")
323 | })
324 | }
325 |
326 | func TestSplitAzIDAndGiveItem(t *testing.T) {
327 | t.Run("ValidInput", func(t *testing.T) {
328 | input := "item1-item2-item3"
329 | separator := "-"
330 | index := 1
331 | expected := "item2"
332 |
333 | result := SplitAzIDAndGiveItem(input, separator, index)
334 |
335 | if result != expected {
336 | t.Errorf("Expected result: %s, got: %s", expected, result)
337 | }
338 | })
339 | }
340 |
341 | func TestGetBackupFileVersion(t *testing.T) {
342 | testCases := []struct {
343 | name string
344 | current int
345 | expected int
346 | }{
347 | {name: "NoBackups",
348 | current: 0,
349 | expected: 0},
350 | {name: "OneBackups",
351 | current: 1,
352 | expected: 1},
353 | {name: "FiveBackups",
354 | current: 4,
355 | expected: 5},
356 | }
357 | for _, tc := range testCases {
358 | directory := t.TempDir()
359 | for i := 0; i < tc.current; i++ {
360 | _, err := os.Create(directory + backupExtnesion + "." + fmt.Sprint(i))
361 | log.Debug(directory + backupExtnesion + "." + fmt.Sprint(i))
362 | core.FailOnError(err, filedtoCopyToTarget)
363 | }
364 | t.Run(tc.name, func(t *testing.T) {
365 | c := CommandOptions{Path: directory}
366 | // Test case: Perfect match, current version is the latest
367 | result := c.GetBackupFileVersion()
368 | assert.Equal(t, tc.expected, result, "Unexpected result for "+tc.name)
369 | })
370 | }
371 | }
372 |
373 | func TestStructOutput(t *testing.T) {
374 | outputStruct := Provider{Provider: "aws", Accounts: []Account{{Name: "my-account", TotalCount: 1, Clusters: []Cluster{{Name: "my-cluster", Version: "1.21", Latest: "1.27", Region: "eu-west-1", Status: "Warning"}}}}}
375 | testCases := []struct {
376 | name string
377 | inpoutInfo interface{}
378 | Command CommandOptions
379 | Path string
380 | Output string
381 | }{
382 | {
383 | name: "json file Saved in current directory",
384 | inpoutInfo: outputStruct,
385 | Command: CommandOptions{Output: "json", Path: "./output"},
386 | },
387 | {
388 | name: "Yaml file Saved in current directory",
389 | inpoutInfo: outputStruct,
390 | Command: CommandOptions{Output: "yaml", Path: "./output"},
391 | },
392 | }
393 |
394 | for _, tc := range testCases {
395 | t.Run(tc.name, func(t *testing.T) {
396 | var c CommandOptions = CommandOptions(tc.Command)
397 | filename := c.Path + "." + c.Output
398 | c.StructOutput(tc.inpoutInfo)
399 | _, err := os.Stat(filename)
400 | if err != nil {
401 | t.Errorf("Expected file '%s' to be created, but got an error: %v", filename, err)
402 | }
403 | err = os.Remove(filename)
404 | if err != nil {
405 | fmt.Printf("Error deleting the file: %s\n", err)
406 | }
407 | })
408 | }
409 | }
410 |
411 | func TestGetYamlOrJsonOutput(t *testing.T) {
412 | inputInfo := Provider{Provider: "aws", Accounts: []Account{{Name: "a", Clusters: []Cluster{{Name: "1", Version: "1.23", Latest: "1.27", Region: "eu-west-1"}}, TotalCount: 1}}}
413 | testCases := []struct {
414 | name string
415 | inpoutInfo interface{}
416 | Command CommandOptions
417 | Expecterd string
418 | }{
419 | {
420 | name: "a valid yaml struct",
421 | inpoutInfo: inputInfo,
422 | Command: CommandOptions{Output: "yaml"},
423 | },
424 | {
425 | name: "a valid json struct",
426 | inpoutInfo: inputInfo,
427 | Command: CommandOptions{Output: "json"},
428 | },
429 | {
430 | name: "Not a valid csv struct",
431 | inpoutInfo: inputInfo,
432 | Command: CommandOptions{Output: "csv"},
433 | },
434 | }
435 |
436 | for _, tc := range testCases {
437 | t.Run(tc.name, func(t *testing.T) {
438 | c := CommandOptions(tc.Command)
439 | data, err := c.getYamlOrJsonOutput(tc.inpoutInfo)
440 | if err != nil {
441 | expectedError := "requested Output is not supported"
442 | assert.Equal(t, expectedError, err.Error())
443 | } else {
444 | assert.IsType(t, []byte{}, data)
445 | }
446 | })
447 | }
448 | }
449 |
--------------------------------------------------------------------------------
/provider/gcp.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "k8f/core"
7 | "strings"
8 |
9 | log "github.com/sirupsen/logrus"
10 | "google.golang.org/api/cloudresourcemanager/v1"
11 | "google.golang.org/api/container/v1"
12 | "google.golang.org/api/option"
13 | )
14 |
15 | // main process for List Command
16 | func (c CommandOptions) GcpMain() Provider {
17 | log.Info("Starting GCP List")
18 | var clusters []Account
19 | Projects := gcpProjects()
20 | chanel := make(chan Account)
21 | for _, p := range Projects {
22 | go func(chanel chan Account, p subs) {
23 | log.Info("Starting GCP project: " + p.Id)
24 | var err error
25 | projclusters, err := c.getK8sClusterConfigs(context.Background(), p.Id)
26 | chanel <- Account{Name: p.Id, Clusters: projclusters, TotalCount: len(projclusters)}
27 | log.Error(err)
28 | }(chanel, p)
29 |
30 | }
31 | for i := 0; i < len(Projects); i++ {
32 | accoutn := <-chanel
33 | clusters = append(clusters, accoutn)
34 | }
35 | return Provider{Provider: "gcp", Accounts: clusters, TotalCount: countTotal(clusters)}
36 | }
37 |
38 | // lists all GCP projects in current orgenization
39 | func gcpProjects() []subs {
40 | // resource manager auth
41 | var projStruct []subs
42 | cloudresourcemanagerService, err := cloudresourcemanager.NewService(ctx, option.WithScopes(cloudresourcemanager.CloudPlatformReadOnlyScope))
43 | core.FailOnError(err, "Failed to create Auth client")
44 | // get list of orginization Projects
45 | projList := cloudresourcemanagerService.Projects.List()
46 | resp, err := projList.Do()
47 | core.FailOnError(err, "Failed to get projects list")
48 | for _, a := range resp.Projects {
49 | projStruct = append(projStruct, subs{Name: a.Name, Id: a.ProjectId})
50 | }
51 | return projStruct
52 | }
53 |
54 | func (c CommandOptions) getK8sClusterConfigs(ctx context.Context, projectId string) ([]Cluster, error) {
55 | var clustserss []Cluster
56 | svc, err := container.NewService(ctx)
57 | if err != nil {
58 | return []Cluster{}, fmt.Errorf("container.NewService: %w", err)
59 | }
60 |
61 | // Ask Google for a list of all kube clusters in the given project.
62 |
63 | resp, err := svc.Projects.Zones.Clusters.List(projectId, "-").Context(ctx).Do()
64 | if err != nil {
65 | return []Cluster{}, fmt.Errorf("clusters list project=%s: %w", projectId, err)
66 | }
67 |
68 | for _, a := range resp.Clusters {
69 | log.Info("the Cluster name is: " + a.Name + " and its in zone " + a.Zone)
70 | clustserss = append(clustserss, Cluster{Name: a.Name, Version: a.CurrentMasterVersion, Region: a.Zone, Latest: c.latestGCP(a)})
71 | }
72 | return clustserss, nil
73 | }
74 |
75 | // func to get latest version
76 | func (c CommandOptions) latestGCP(k *container.Cluster) string {
77 | svc, err := container.NewService(context.Background())
78 | core.FailOnError(err, "failed to create container service")
79 | output := svc.Projects.Zones.GetServerconfig(k.Name, k.Zone)
80 | ver, err := output.Do()
81 | core.FailOnError(err, "failed to get versions")
82 | for _, v := range ver.Channels {
83 | if strings.Contains(k.ReleaseChannel.Channel, v.Channel) {
84 | return v.ValidVersions[0]
85 | }
86 | }
87 | return ""
88 | }
89 |
--------------------------------------------------------------------------------
/provider/interfaces.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/aws/aws-sdk-go-v2/service/sts"
7 | )
8 |
9 | type STSAssumeRoleAPI interface {
10 | AssumeRole(ctx context.Context,
11 | params *sts.AssumeRoleInput,
12 | optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error)
13 | }
14 |
--------------------------------------------------------------------------------
/provider/kube_merge.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "crypto/sha256"
5 | "encoding/base64"
6 | "errors"
7 | "fmt"
8 | "k8f/core"
9 | "strings"
10 |
11 | "github.com/imdario/mergo"
12 | "github.com/manifoldco/promptui"
13 | log "github.com/sirupsen/logrus"
14 | "github.com/spf13/cobra"
15 | "k8s.io/client-go/tools/clientcmd"
16 | clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
17 | )
18 |
19 | // MergeCommand merge cmd struct
20 | type MergeCommand struct {
21 | cobra.Command
22 | }
23 |
24 | // KubeConfigOption kubeConfig option
25 | type KubeConfigOption struct {
26 | config *clientcmdapi.Config
27 | fileName string
28 | }
29 |
30 | func (mc CommandOptions) runMerge(newConf Config) ([]byte, error) {
31 | var kconfigs []*clientcmdapi.Config
32 | confToupdate, err := toClientConfig(&newConf)
33 | kconfigs = append(kconfigs, confToupdate)
34 | core.FailOnError(err, "failed to convert Config struct to clientcmdapi.Config")
35 | outConfigs := clientcmdapi.NewConfig()
36 | log.Infof("Loading KubeConfig file: %s\n", mc.Path)
37 | loadConfig, err := loadKubeConfig(mc.Path)
38 | core.FailOnError(err, "File "+mc.Path+" is not kubeconfig\n")
39 | kconfigs = append(kconfigs, loadConfig)
40 | for _, conf := range kconfigs {
41 | kco := &KubeConfigOption{
42 | config: conf,
43 | fileName: getFileName(mc.Path),
44 | }
45 | outConfigs, err = kco.handleContexts(outConfigs, mc)
46 | if err != nil {
47 | return nil, err
48 | }
49 | }
50 | outConfigs.APIVersion = "v1"
51 | outConfigs.Kind = "Config"
52 | var contxetcItem *clientcmdapi.Context
53 | for _, value := range outConfigs.Contexts {
54 | contxetcItem = value
55 | break
56 | }
57 | outConfigs.CurrentContext = contxetcItem.Cluster
58 | confByte, err := clientcmd.Write(*outConfigs)
59 | if err != nil {
60 | return nil, err
61 | }
62 | return confByte, nil
63 | }
64 |
65 | func loadKubeConfig(yaml string) (*clientcmdapi.Config, error) {
66 | loadConfig, err := clientcmd.LoadFromFile(yaml)
67 | if err != nil {
68 | return nil, err
69 | }
70 | return loadConfig, err
71 | }
72 |
73 | func getFileName(path string) string {
74 | n := strings.Split(path, "/")
75 | result := strings.Split(n[len(n)-1], ".")
76 | return result[0]
77 | }
78 |
79 | func (kc *KubeConfigOption) handleContexts(oldConfig *clientcmdapi.Config, mc CommandOptions) (*clientcmdapi.Config, error) {
80 | newConfig := clientcmdapi.NewConfig()
81 | for name, ctx := range kc.config.Contexts {
82 | var newName string
83 | if len(kc.config.Contexts) >= 1 {
84 | newName = name
85 | } else {
86 | newName = kc.fileName
87 | }
88 | if checkContextName(newName, oldConfig) && !mc.ForceMerge {
89 | nameConfirm := BoolUI(fmt.Sprintf("「%s」 Name already exists, do you want to rename it. (If you select `False`, this context will not be merged)", newName), mc)
90 | if nameConfirm == "True" {
91 | newName = core.PromptUI("Rename", newName)
92 | if newName == kc.fileName {
93 | return nil, errors.New("need to rename")
94 | }
95 | } else {
96 | continue
97 | }
98 | }
99 | itemConfig := kc.handleContext(oldConfig, newName, ctx)
100 | newConfig = appendConfig(newConfig, itemConfig)
101 | log.Infof("Add Context: %s \n", newName)
102 | }
103 | outConfig := appendConfig(oldConfig, newConfig)
104 | return outConfig, nil
105 | }
106 | func checkContextName(name string, oldConfig *clientcmdapi.Config) bool {
107 | if _, ok := oldConfig.Contexts[name]; ok {
108 | return true
109 | }
110 | return false
111 | }
112 | func BoolUI(label string, mc CommandOptions) string {
113 | templates := &promptui.SelectTemplates{
114 | Label: "{{ . }}",
115 | Active: "\U0001F37A {{ . | red }}",
116 | Inactive: " {{ . | cyan }}",
117 | Selected: "\U0001F47B {{ . | green }}",
118 | }
119 | prompt := promptui.Select{
120 | Label: label,
121 | Items: []string{"False", "True"},
122 | Templates: templates,
123 | Size: mc.UiSize,
124 | }
125 | _, obj, err := prompt.Run()
126 | if err != nil {
127 | log.Fatalf("Prompt failed %v\n", err)
128 | }
129 | return obj
130 | }
131 |
132 | // HashSufString return the string of HashSuf.
133 | func HashSufString(data string) string {
134 | sum, _ := hEncode(Hash(data))
135 | return sum
136 | }
137 | func (kc *KubeConfigOption) handleContext(oldConfig *clientcmdapi.Config,
138 | name string, ctx *clientcmdapi.Context) *clientcmdapi.Config {
139 |
140 | var (
141 | clusterNameSuffix string
142 | userNameSuffix string
143 | )
144 |
145 | isClusterNameExist, isUserNameExist := checkClusterAndUserName(oldConfig, ctx.Cluster, ctx.AuthInfo)
146 | newConfig := clientcmdapi.NewConfig()
147 | suffix := HashSufString(name)
148 |
149 | if isClusterNameExist {
150 | clusterNameSuffix = "-" + suffix
151 | }
152 | if isUserNameExist {
153 | userNameSuffix = "-" + suffix
154 | }
155 |
156 | userName := fmt.Sprintf("%v%v", ctx.AuthInfo, userNameSuffix)
157 | clusterName := fmt.Sprintf("%v%v", ctx.Cluster, clusterNameSuffix)
158 | newCtx := ctx.DeepCopy()
159 | newConfig.AuthInfos[userName] = kc.config.AuthInfos[newCtx.AuthInfo]
160 | newConfig.Clusters[clusterName] = kc.config.Clusters[newCtx.Cluster]
161 | newConfig.Contexts[name] = newCtx
162 | newConfig.Contexts[name].AuthInfo = userName
163 | newConfig.Contexts[name].Cluster = clusterName
164 |
165 | return newConfig
166 | }
167 | func checkClusterAndUserName(oldConfig *clientcmdapi.Config, newClusterName, newUserName string) (bool, bool) {
168 | var (
169 | isClusterNameExist bool
170 | isUserNameExist bool
171 | )
172 |
173 | for _, ctx := range oldConfig.Contexts {
174 | if ctx.Cluster == newClusterName {
175 | isClusterNameExist = true
176 | }
177 | if ctx.AuthInfo == newUserName {
178 | isUserNameExist = true
179 | }
180 | }
181 |
182 | return isClusterNameExist, isUserNameExist
183 | }
184 |
185 | // Copied from https://github.com/kubernetes/kubernetes
186 | // /blob/master/pkg/kubectl/util/hash/hash.go
187 | func hEncode(hex string) (string, error) {
188 | if len(hex) < 10 {
189 | return "", fmt.Errorf(
190 | "input length must be at least 10")
191 | }
192 | enc := []rune(hex[:10])
193 | for i := range enc {
194 | switch enc[i] {
195 | case '0':
196 | enc[i] = 'g'
197 | case '1':
198 | enc[i] = 'h'
199 | case '3':
200 | enc[i] = 'k'
201 | case 'a':
202 | enc[i] = 'm'
203 | case 'e':
204 | enc[i] = 't'
205 | }
206 | }
207 | return string(enc), nil
208 | }
209 |
210 | func appendConfig(c1, c2 *clientcmdapi.Config) *clientcmdapi.Config {
211 | config := clientcmdapi.NewConfig()
212 | _ = mergo.Merge(config, c1)
213 | _ = mergo.Merge(config, c2)
214 | return config
215 | }
216 |
217 | // Hash returns the hex form of the sha256 of the argument.
218 | func Hash(data string) string {
219 | return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
220 | }
221 |
222 | // Convert merged config to clientcmdapi.Config
223 | func toClientConfig(cfg *Config) (*clientcmdapi.Config, error) {
224 | clientConfig := clientcmdapi.NewConfig()
225 |
226 | // Set API version
227 | clientConfig.APIVersion = cfg.APIVersion
228 |
229 | // Set current context
230 | clientConfig.CurrentContext = cfg.CurrentContext
231 |
232 | // Set clusters
233 | for _, c := range cfg.Clusters {
234 | decodedBytes, err := base64.StdEncoding.DecodeString(c.Cluster.CertificateAuthorityData)
235 | core.FailOnError(err, decodeError)
236 | cluster := clientcmdapi.Cluster{
237 | Server: c.Cluster.Server,
238 | CertificateAuthorityData: decodedBytes,
239 | }
240 | clientConfig.Clusters[c.Name] = &cluster
241 | }
242 |
243 | // Set users
244 | for _, u := range cfg.Users {
245 | user := getUserForCluster(u)
246 | clientConfig.AuthInfos[u.Name] = &user
247 | }
248 |
249 | // Set contexts
250 | for _, c := range cfg.Contexts {
251 | context := clientcmdapi.Context{
252 | Cluster: c.Context.Cluster,
253 | AuthInfo: c.Context.User,
254 | }
255 | clientConfig.Contexts[c.Name] = &context
256 | }
257 |
258 | return clientConfig, nil
259 | }
260 |
261 | func getUserForCluster(u Users) clientcmdapi.AuthInfo {
262 | var user clientcmdapi.AuthInfo
263 | if !checkIfStructInit(u.User, "exec") {
264 | clientCertificateDataBytes, err := base64.StdEncoding.DecodeString(u.User.ClientCertificateData)
265 | core.FailOnError(err, decodeError)
266 | ClientKeyDataBytes, err := base64.StdEncoding.DecodeString(u.User.ClientKeyData)
267 | core.FailOnError(err, decodeError)
268 | user = clientcmdapi.AuthInfo{
269 | ClientCertificateData: []byte(clientCertificateDataBytes),
270 | ClientKeyData: []byte(ClientKeyDataBytes),
271 | Token: u.User.Token,
272 | }
273 | } else {
274 | user = clientcmdapi.AuthInfo{
275 | Exec: &clientcmdapi.ExecConfig{
276 | APIVersion: u.User.Exec.APIVersion,
277 | Command: u.User.Exec.Command,
278 | Args: u.User.Exec.Args,
279 | Env: []clientcmdapi.ExecEnvVar{},
280 | },
281 | }
282 | envs, _ := u.User.Exec.Env.([]Env)
283 | for _, env := range envs {
284 | user.Exec.Env = append(user.Exec.Env, clientcmdapi.ExecEnvVar{Name: env.Name, Value: env.Value})
285 | }
286 | }
287 | return user
288 | }
289 |
--------------------------------------------------------------------------------
/provider/structs.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | type AwsProfiles struct {
4 | Name string
5 | IsRole bool
6 | Arn string `json:"arn,omitempty"`
7 | ConfProfile string
8 | SessionName string
9 | }
10 |
11 | type CommandOptions struct {
12 | AwsRegion string
13 | Path string
14 | Output string
15 | Overwrite bool
16 | Combined bool
17 | Backup bool
18 | Merge bool
19 | ForceMerge bool
20 | UiSize int
21 | DryRun bool
22 | AwsAuth bool
23 | AwsRoleString string
24 | AwsEnvProfile bool
25 | AwsClusterName bool
26 | ProfileName string
27 | ProfileSelector bool
28 | SaveOutput bool
29 | Validate bool
30 | }
31 |
32 | // Azure /GCP
33 | type subs struct {
34 | Name string `json:"name,omitempty"`
35 | Id string `json:"id,omitempty"`
36 | }
37 |
38 | // Standard of Cluster Info Output
39 | type Cluster struct {
40 | Name string `json:"name,omitempty"`
41 | Version string `json:"version,omitempty"`
42 | Latest string `json:"latest,omitempty"`
43 | Region string `json:"region,omitempty"`
44 | Id string `json:"id,omitempty"`
45 | CluserChannel string `json:"channel,omitempty"`
46 | Status string `json:"status,omitempty"`
47 | }
48 |
49 | type Account struct {
50 | Name string `json:"name,omitempty"`
51 | Clusters []Cluster `json:"clusters,omitempty"`
52 | TotalCount int `json:"totalCount,omitempty"`
53 | Tenanat string `json:"tenant,omitempty"`
54 | }
55 |
56 | type Provider struct {
57 | Provider string `json:"provider,omitempty"`
58 | Accounts []Account `json:"accounts,omitempty"`
59 | TotalCount int `json:"totalCount,omitempty"`
60 | }
61 |
62 | // AWS Kubeconfig
63 | type LocalConfig struct {
64 | Authinfo User `json:"authinfo,omitempty"`
65 | Context Context `json:"context,omitempty"`
66 | Cluster CCluster `json:"cluster,omitempty"`
67 | }
68 |
69 | type AllConfig struct {
70 | auth []Users
71 | context []Contexts
72 | clusters []Clusters
73 | }
74 |
75 | // merged config struct
76 | type Config struct {
77 | APIVersion string `yaml:"apiVersion,omitempty"`
78 | Clusters []Clusters `yaml:"clusters,omitempty"`
79 | Contexts []Contexts `yaml:"contexts,omitempty"`
80 | CurrentContext string `yaml:"current-context,omitempty"`
81 | Kind string `yaml:"kind,omitempty"`
82 | Preferences Preferences `yaml:"preferences,omitempty"`
83 | Users []Users `yaml:"users,omitempty"`
84 | }
85 |
86 | type Clusters struct {
87 | Cluster CCluster `yaml:"cluster,omitempty"`
88 | Name string `yaml:"name,omitempty"`
89 | }
90 |
91 | type CCluster struct {
92 | CertificateAuthorityData string `yaml:"certificate-authority-data,omitempty"`
93 | Server string `yaml:"server,omitempty"`
94 | }
95 |
96 | type Context struct {
97 | Cluster string `yaml:"cluster,omitempty"`
98 | User string `yaml:"user,omitempty"`
99 | }
100 |
101 | type Contexts struct {
102 | Context Context `yaml:"context,omitempty"`
103 | Name string `yaml:"name,omitempty"`
104 | }
105 |
106 | type Preferences struct {
107 | }
108 |
109 | type Exec struct {
110 | APIVersion string `yaml:"apiVersion,omitempty"`
111 | Args []string `yaml:"args,omitempty"`
112 | Command string `yaml:"command,omitempty"`
113 | Env interface{} `yaml:"env,omitempty"`
114 | ProvideClusterInfo bool `yaml:"provideClusterInfo,omitempty"`
115 | }
116 |
117 | type User struct {
118 | Exec Exec `yaml:"exec,omitempty"`
119 | ClientCertificateData string `yaml:"client-certificate-data,omitempty"`
120 | ClientKeyData string `yaml:"client-key-data,omitempty"`
121 | Token string `yaml:"token,omitempty"`
122 | }
123 |
124 | type Users struct {
125 | Name string `yaml:"name,omitempty"`
126 | User User `yaml:"user,omitempty"`
127 | }
128 |
129 | type Env struct {
130 | Name string `yaml:"name,omitempty"`
131 | Value string `yaml:"value,omitempty"`
132 | }
133 |
--------------------------------------------------------------------------------
/sonar-project.properties:
--------------------------------------------------------------------------------
1 | # =====================================================
2 | # Standard properties
3 | # =====================================================
4 | sonar.projectKey=AdamRussak_k8f_AYi6D0uVa-POq_Tc4jQY
5 | sonar.sources=.
6 | sonar.exclusions=**/*_test.go
7 | sonar.tests=.
8 | sonar.test.inclusions=**/*_test.go
9 | # =====================================================
10 | # Meta-data for the project
11 | # =====================================================
12 | sonar.links.homepage=https://github.com/AdamRussak/k8f
13 | sonar.links.scm=https://github.com/AdamRussak/k8f
14 | sonar.links.issue=https://github.com/AdamRussak/k8f/issues
15 |
16 | # =====================================================
17 | # Properties specific to Go
18 | # =====================================================
19 | # sonar.go.gometalinter.reportPaths=gometalinter-report.out
20 | sonar.go.tests.reportPaths=report.json
21 | sonar.go.coverage.reportPaths=coverage.out
--------------------------------------------------------------------------------
/test/config:
--------------------------------------------------------------------------------
1 | [default]
2 | region = eu-west-1
3 | [profile account1]
4 | role_arn = arn:aws:iam::123456789012:role/my-role
5 | source_profile = default
6 | [profile account2]
7 | role_arn = arn:aws:iam::125456389012:role/my-role
8 | source_profile = default
9 |
10 |
--------------------------------------------------------------------------------
/test/credentials:
--------------------------------------------------------------------------------
1 | [default]
2 | aws_access_key_id = myKey
3 | aws_secret_access_key = myAccessKey
4 | [account1]
5 | aws_access_key_id = account1Key
6 | aws_secret_access_key = account1AccessKey
7 | [account2]
8 | aws_access_key_id = account2Key
9 | aws_secret_access_key = account2AccessKey
10 |
--------------------------------------------------------------------------------