Skip to content

Commit

Permalink
feat: migrate UI to cobra and viper
Browse files Browse the repository at this point in the history
  • Loading branch information
Zebradil committed May 5, 2024
1 parent 043cf65 commit 364d3fa
Show file tree
Hide file tree
Showing 4 changed files with 356 additions and 166 deletions.
262 changes: 262 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
package cmd

import (
"bytes"
"context"
"fmt"
"html/template"
"io"
"os"
"strings"
"sync"

log "github.com/sirupsen/logrus"

crm "google.golang.org/api/cloudresourcemanager/v1"
cnt "google.golang.org/api/container/v1"
su "google.golang.org/api/serviceusage/v1"

"github.com/spf13/cobra"
"github.com/spf13/viper"
)

const KubeconfigBaseTemplate = `
{{- $longID := printf "gke_%s_%s_%s" .ProjectID .Location .ClusterName -}}
---
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority-data: {{ .CertificateAuthorityData }}
server: {{ .Server }}
name: {{ $longID }}
contexts:
- context:
cluster: {{ $longID }}
user: {{ $longID }}
name: <CONTEXT_NAME>
preferences: {}
users:
- name: {{ $longID }}
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: gke-gcloud-auth-plugin
installHint:
Install gke-gcloud-auth-plugin for use with kubectl by following
https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl#install_plugin
provideClusterInfo: true
`

const longDescription = `gke-kubeconfiger discovers GKE clusters and generates kubeconfig files for them.`

var cfgFile string

func init() {
cobra.OnInitialize(initConfig)
}

func initConfig() {
viper.SetEnvPrefix("GKEKC")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
viper.AutomaticEnv()

if cfgFile == "" {
cfgFile = viper.GetString("config")
}

if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
home, err := os.UserHomeDir()
cobra.CheckErr(err)

viper.AddConfigPath(home)
viper.SetConfigType("yaml")
viper.SetConfigName(".gke-kubeconfiger")
}

if err := viper.ReadInConfig(); err == nil {
log.Info("Using config file:", viper.ConfigFileUsed())
}
}

func NewRootCmd(version, commit, date string) *cobra.Command {
rootCmd := &cobra.Command{
Use: "gke-kubeconfiger",
Short: "Discovers GKE clusters and generates kubeconfig files for them.",
Long: longDescription,
Args: cobra.NoArgs,
Version: fmt.Sprintf("%s, commit %s, built at %s", version, commit, date),
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
level, err := log.ParseLevel(viper.GetString("log-level"))
if err != nil {
return err
}
log.Info(cmd.Name(), " version ", cmd.Version)
log.Info("Setting log level to:", level)
log.SetLevel(level)
return nil
},
Run: run,
}

rootCmd.
PersistentFlags().
StringVar(&cfgFile, "config", "", "config file (default is $HOME/.gke-kubeconfiger.yaml)")

// rootCmd.
// Flags().
// StringSlice("projects", []string{}, "Projects to filter by.")

rootCmd.
Flags().
Bool("rename", false, "Rename kubeconfig contexts")

rootCmd.
Flags().
String("rename-tpl", "{{ .ProjectID }}/{{ .Location }}/{{ .ClusterName }}", "Rename template")

rootCmd.
Flags().
String("log-level", "info", "Sets logging level: trace, debug, info, warning, error, fatal, panic.")

rootCmd.
Flags().
Int("batch-size", 10, "Batch size")

err := viper.BindPFlags(rootCmd.Flags())
if err != nil {
log.WithError(err).Fatal("Couldn't bind flags")
}

return rootCmd
}

func run(cmd *cobra.Command, args []string) {
if viper.ConfigFileUsed() != "" {
log.WithField("config", viper.ConfigFileUsed()).Debug("Using config file")
} else {
log.Debug("No config file used")
}

rename := viper.GetBool("rename")
renameTpl := viper.GetString("rename-tpl")
batchSize := viper.GetInt("batch-size")

contextNameTpl := "{{ $longID }}"
if rename {
contextNameTpl = renameTpl
}

kubeconfigTemplate, err := template.New("kubeconfig").Parse(strings.ReplaceAll(KubeconfigBaseTemplate, "<CONTEXT_NAME>", contextNameTpl))
if err != nil {
log.Fatalf("Failed to parse kubeconfig template: %v", err)
}

return

projects := make(chan *crm.Project, batchSize)
filteredProjects := make(chan *crm.Project, batchSize)
completed := make(chan bool)

go getProjects(projects)
go filterProjects(projects, filteredProjects)
go getCredentials(filteredProjects, kubeconfigTemplate, completed)

for range completed {
}
}

func getProjects(out chan<- *crm.Project) {
ctx := context.Background()
crmService, err := crm.NewService(ctx)
if err != nil {
log.Fatalf("Failed to create cloudresourcemanager service: %v", err)
}
projects, err := crmService.Projects.List().Do()
if err != nil {
log.Fatalf("Failed to list projects: %v", err)
}
for _, project := range projects.Projects {
out <- project
}
close(out)
}

func filterProjects(in <-chan *crm.Project, out chan<- *crm.Project) {
ctx := context.Background()
suService, err := su.NewService(ctx)
if err != nil {
log.Fatalf("Failed to create serviceusage service: %v", err)
}
suServicesService := su.NewServicesService(suService)
wg := sync.WaitGroup{}
for project := range in {
wg.Add(1)
go func(project *crm.Project) {
fmt.Printf("Project: %s (%s)\n", project.Name, project.ProjectId)
containerServiceRes, err := suServicesService.Get(fmt.Sprintf("projects/%s/services/container.googleapis.com", project.ProjectId)).Do()
if err != nil {
log.Fatalf("Failed to get container service: %v", err)
}
if containerServiceRes.State == "ENABLED" {
out <- project
}
wg.Done()
}(project)
}
wg.Wait()
close(out)
}

func getCredentials(in <-chan *crm.Project, kubeconfigTemplate *template.Template, completed chan<- bool) {
ctx := context.Background()
containerService, err := cnt.NewService(ctx)
if err != nil {
log.Fatalf("Failed to create container service: %v", err)
}
wg := sync.WaitGroup{}
for project := range in {
wg.Add(1)
go func(project *crm.Project) {
clusters, err := containerService.Projects.Locations.Clusters.List(fmt.Sprintf("projects/%s/locations/-", project.ProjectId)).Do()
if err != nil {
log.Fatalf("Failed to list clusters: %v", err)
}
for _, cluster := range clusters.Clusters {
wg.Add(1)
go func(cluster *cnt.Cluster) {
fmt.Printf("Cluster: %s (%s)\n", cluster.Name, cluster.Location)
endpoint := fmt.Sprintf("https://%s", cluster.Endpoint)
cert := cluster.MasterAuth.ClusterCaCertificate
kubeconfig := &bytes.Buffer{}
err = kubeconfigTemplate.Execute(kubeconfig, map[string]string{
"CertificateAuthorityData": cert,
"Server": endpoint,
"ProjectID": project.ProjectId,
"Location": cluster.Location,
"ClusterName": cluster.Name,
})
if err != nil {
log.Fatalf("Failed to execute kubeconfig template: %v", err)
}
filename := fmt.Sprintf("%s_%s_%s.yaml", project.ProjectId, cluster.Location, cluster.Name)
out, err := os.Create(filename)
if err != nil {
log.Fatalf("Failed to create file: %v", err)
}
defer out.Close()
_, err = io.Copy(out, kubeconfig)
if err != nil {
log.Fatalf("Failed to write file: %v", err)
}
wg.Done()
}(cluster)
}
wg.Done()
}(project)
}
wg.Wait()
close(completed)
}
25 changes: 24 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ module gker

go 1.22.2

require google.golang.org/api v0.177.0
require (
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.18.2
google.golang.org/api v0.177.0
)

require (
cloud.google.com/go/auth v0.3.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
Expand All @@ -17,17 +23,34 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/oauth2 v0.19.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect
google.golang.org/grpc v1.63.2 // indirect
google.golang.org/protobuf v1.34.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

0 comments on commit 364d3fa

Please sign in to comment.