github.com/codefresh-io/kcfi@v0.0.0-20230301195427-c1578715cc46/cmd/kcfi/main.go (about)

     1  /*
     2  Copyright The Codefresh Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"flag"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"log"
    24  	"os"
    25  	"strings"
    26  
    27  	"github.com/spf13/cobra"
    28  	"github.com/spf13/pflag"
    29  	"github.com/spf13/viper"
    30  
    31  	"k8s.io/klog"
    32  	"sigs.k8s.io/yaml"
    33  
    34  	// Import to initialize client auth plugins.
    35  	_ "k8s.io/client-go/plugin/pkg/client/auth"
    36  
    37  	"helm.sh/helm/v3/pkg/action"
    38  	"helm.sh/helm/v3/pkg/cli"
    39  	"helm.sh/helm/v3/pkg/gates"
    40  	"helm.sh/helm/v3/pkg/kube"
    41  	kubefake "helm.sh/helm/v3/pkg/kube/fake"
    42  	"helm.sh/helm/v3/pkg/release"
    43  	"helm.sh/helm/v3/pkg/storage/driver"
    44  
    45  	c "github.com/codefresh-io/kcfi/pkg/config"
    46  )
    47  
    48  // FeatureGateOCI is the feature gate for checking if `helm chart` and `helm registry` commands should work
    49  const FeatureGateOCI = gates.Gate("HELM_EXPERIMENTAL_OCI")
    50  
    51  var (
    52  	settings         *cli.EnvSettings
    53  	defaultNamespace = "codefresh"
    54  
    55  	flagConfig = "config"
    56  )
    57  
    58  var configuredNamespace string
    59  
    60  func init() {
    61  	log.SetFlags(log.Lshortfile)
    62  	//Set Codefresh default namespace
    63  	// if _, ok := os.LookupEnv("HELM_NAMESPACE"); !ok {
    64  	// 	os.Setenv("HELM_NAMESPACE", defaultNamespace)
    65  	// }
    66  	settings = cli.New()
    67  }
    68  
    69  func debug(format string, v ...interface{}) {
    70  	if settings.Debug {
    71  		format = fmt.Sprintf("[debug] %s\n", format)
    72  		log.Output(2, fmt.Sprintf(format, v...))
    73  	}
    74  }
    75  
    76  func initKubeLogs() {
    77  	pflag.CommandLine.SetNormalizeFunc(wordSepNormalizeFunc)
    78  	gofs := flag.NewFlagSet("klog", flag.ExitOnError)
    79  	klog.InitFlags(gofs)
    80  	pflag.CommandLine.AddGoFlagSet(gofs)
    81  	pflag.CommandLine.Set("logtostderr", "true")
    82  }
    83  
    84  func main() {
    85  	initKubeLogs()
    86  
    87  	actionConfig := new(action.Configuration)
    88  	cmd := newRootCmd(actionConfig, os.Stdout, os.Args[1:])
    89  
    90  	configuredNamespace = settings.Namespace()
    91  	// setting kubernetes client namespace, kube-context, kubeconfig
    92  	// priorities:
    93  	//   1. use flags --namespace, --kube-context, --kubeconfig. they are already set in o.Helm.*
    94  	//   2. use values specified in config.yaml: {kubernetes: {namespace: "", kube-context: "", kubectonfig}}
    95  	//   3. default context and namespace defined in ~/.kube/config
    96  	// finding the command being executing and parse its config file to set kube client
    97  	// we should do it here in main() because helm sets kube client here
    98  	childCmd, childArgs, _ := cmd.Find(os.Args[1:])
    99  	childFlags := childCmd.Flags()
   100  	configFlag := childFlags.Lookup(flagConfig)
   101  	if configFlag != nil {
   102  		//merging config file kubernetes parameters into settings
   103  		childFlags.Parse(childArgs)
   104  		configFileName := configFlag.Value.String()
   105  		if configFileName != "" {
   106  			viper.SetConfigFile(configFileName)
   107  			if err := viper.ReadInConfig(); err == nil {
   108  				debug("Using config file: %s", viper.ConfigFileUsed())
   109  
   110  				viper.BindPFlag(c.KeyKubeNamespace, childFlags.Lookup("namespace"))
   111  				viper.BindPFlag(c.KeyKubeContext, childFlags.Lookup("kube-context"))
   112  				viper.BindPFlag(c.KeyKubeKubeconfig, childFlags.Lookup("kubeconfig"))
   113  
   114  				if ns := viper.GetString(c.KeyKubeNamespace); ns != "" {
   115  					configuredNamespace = ns
   116  				}
   117  				if kubeContext := viper.GetString(c.KeyKubeContext); kubeContext != "" {
   118  					settings.KubeContext = kubeContext
   119  				}
   120  				if kubeconfig := viper.GetString(c.KeyKubeKubeconfig); kubeconfig != "" {
   121  					settings.KubeConfig = kubeconfig
   122  				}
   123  			}
   124  		}
   125  	}
   126  
   127  	kubeClientConfig := kube.GetConfig(settings.KubeConfig, settings.KubeContext, configuredNamespace)
   128  	if settings.KubeToken != "" {
   129  		kubeClientConfig.BearerToken = &settings.KubeToken
   130  	}
   131  	if settings.KubeAPIServer != "" {
   132  		kubeClientConfig.APIServer = &settings.KubeAPIServer
   133  	}
   134  
   135  	helmDriver := os.Getenv("HELM_DRIVER")
   136  	debug("Initializing Kubernetes client: namespace=%s kube-context=%s kubeconfig =%s", configuredNamespace, settings.KubeContext, settings.KubeConfig)
   137  	if err := actionConfig.Init(kubeClientConfig, configuredNamespace, helmDriver, debug); err != nil {
   138  		log.Fatal(err)
   139  	}
   140  	if helmDriver == "memory" {
   141  		loadReleasesInMemory(actionConfig)
   142  	}
   143  
   144  	if err := cmd.Execute(); err != nil {
   145  		debug("%+v", err)
   146  		switch e := err.(type) {
   147  		case pluginError:
   148  			os.Exit(e.code)
   149  		default:
   150  			os.Exit(1)
   151  		}
   152  	}
   153  }
   154  
   155  // wordSepNormalizeFunc changes all flags that contain "_" separators
   156  func wordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
   157  	return pflag.NormalizedName(strings.ReplaceAll(name, "_", "-"))
   158  }
   159  
   160  func checkOCIFeatureGate() func(_ *cobra.Command, _ []string) error {
   161  	return func(_ *cobra.Command, _ []string) error {
   162  		if !FeatureGateOCI.IsEnabled() {
   163  			return FeatureGateOCI.Error()
   164  		}
   165  		return nil
   166  	}
   167  }
   168  
   169  // This function loads releases into the memory storage if the
   170  // environment variable is properly set.
   171  func loadReleasesInMemory(actionConfig *action.Configuration) {
   172  	filePaths := strings.Split(os.Getenv("HELM_MEMORY_DRIVER_DATA"), ":")
   173  	if len(filePaths) == 0 {
   174  		return
   175  	}
   176  
   177  	store := actionConfig.Releases
   178  	mem, ok := store.Driver.(*driver.Memory)
   179  	if !ok {
   180  		// For an unexpected reason we are not dealing with the memory storage driver.
   181  		return
   182  	}
   183  
   184  	actionConfig.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard}
   185  
   186  	for _, path := range filePaths {
   187  		b, err := ioutil.ReadFile(path)
   188  		if err != nil {
   189  			log.Fatal("Unable to read memory driver data", err)
   190  		}
   191  
   192  		releases := []*release.Release{}
   193  		if err := yaml.Unmarshal(b, &releases); err != nil {
   194  			log.Fatal("Unable to unmarshal memory driver data: ", err)
   195  		}
   196  
   197  		for _, rel := range releases {
   198  			if err := store.Create(rel); err != nil {
   199  				log.Fatal(err)
   200  			}
   201  		}
   202  	}
   203  	// Must reset namespace to the proper one
   204  	mem.SetNamespace(settings.Namespace())
   205  }