github.com/jenkins-x/jx/v2@v2.1.155/pkg/cmd/step/boot/step_boot_vault.go (about)

     1  package boot
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  	"text/template"
    11  
    12  	"github.com/jenkins-x/jx-logging/pkg/log"
    13  	"github.com/jenkins-x/jx/v2/pkg/cloud"
    14  	gkevault "github.com/jenkins-x/jx/v2/pkg/cloud/gke/vault"
    15  	"github.com/jenkins-x/jx/v2/pkg/cmd/helper"
    16  	"github.com/jenkins-x/jx/v2/pkg/cmd/opts"
    17  	"github.com/jenkins-x/jx/v2/pkg/cmd/templates"
    18  	"github.com/jenkins-x/jx/v2/pkg/config"
    19  	"github.com/jenkins-x/jx/v2/pkg/helm"
    20  	"github.com/jenkins-x/jx/v2/pkg/io/secrets"
    21  	"github.com/jenkins-x/jx/v2/pkg/kube"
    22  	kubevault "github.com/jenkins-x/jx/v2/pkg/kube/vault"
    23  	"github.com/jenkins-x/jx/v2/pkg/util"
    24  	"github.com/jenkins-x/jx/v2/pkg/vault"
    25  	pkgvault "github.com/jenkins-x/jx/v2/pkg/vault"
    26  	"github.com/jenkins-x/jx/v2/pkg/vault/create"
    27  	"github.com/pkg/errors"
    28  	"github.com/spf13/cobra"
    29  	corev1 "k8s.io/api/core/v1"
    30  	"k8s.io/client-go/kubernetes"
    31  	"k8s.io/helm/pkg/chartutil"
    32  )
    33  
    34  type vaultSelector struct {
    35  	vaultURL           string
    36  	serviceAccountName string
    37  	namespace          string
    38  }
    39  
    40  func NewVaultSelector(vault vault.Vault) kubevault.Selector {
    41  	selector := &vaultSelector{
    42  		vaultURL:           vault.URL,
    43  		serviceAccountName: vault.ServiceAccountName,
    44  		namespace:          vault.Namespace,
    45  	}
    46  	return selector
    47  }
    48  
    49  // GetVault retrieve the given vault by name
    50  func (v *vaultSelector) GetVault(name string, namespace string, useIngressURL bool) (*vault.Vault, error) {
    51  	vault := vault.Vault{
    52  		Name:               name,
    53  		Namespace:          namespace,
    54  		URL:                v.vaultURL,
    55  		ServiceAccountName: v.serviceAccountName,
    56  	}
    57  
    58  	return &vault, nil
    59  }
    60  
    61  // StepBootVaultOptions contains the command line flags
    62  type StepBootVaultOptions struct {
    63  	*opts.CommonOptions
    64  	Dir               string
    65  	ProviderValuesDir string
    66  	Namespace         string
    67  }
    68  
    69  var (
    70  	stepBootVaultLong = templates.LongDesc(`
    71  		This step boots up Vault in the current cluster if its enabled in the 'jx-requirements.yml' file and is not already installed.
    72  
    73  		This step is intended to be used in the Jenkins X Boot Pipeline: https://jenkins-x.io/docs/getting-started/setup/boot/
    74  `)
    75  
    76  	stepBootVaultExample = templates.Examples(`
    77  		# boots up Vault if its required
    78  		jx step boot vault
    79  `)
    80  )
    81  
    82  // NewCmdStepBootVault creates the command
    83  func NewCmdStepBootVault(commonOpts *opts.CommonOptions) *cobra.Command {
    84  	o := StepBootVaultOptions{
    85  		CommonOptions: commonOpts,
    86  	}
    87  	cmd := &cobra.Command{
    88  		Use:     "vault",
    89  		Short:   "This step boots up Vault in the current cluster if its enabled in the 'jx-requirements.yml' file and is not already installed",
    90  		Long:    stepBootVaultLong,
    91  		Example: stepBootVaultExample,
    92  		Run: func(cmd *cobra.Command, args []string) {
    93  			o.Cmd = cmd
    94  			o.Args = args
    95  			err := o.Run()
    96  			helper.CheckErr(err)
    97  		},
    98  	}
    99  	cmd.Flags().StringVarP(&o.Dir, "dir", "d", ".", fmt.Sprintf("the directory to look for the requirements file: %s", config.RequirementsConfigFileName))
   100  	cmd.Flags().StringVarP(&o.ProviderValuesDir, "provider-values-dir", "", "", "The optional directory of kubernetes provider specific files")
   101  	cmd.Flags().StringVarP(&o.Namespace, "namespace", "", "", "the namespace that Jenkins X will be booted into. If not specified it defaults to $DEPLOY_NAMESPACE")
   102  
   103  	return cmd
   104  }
   105  
   106  // Run runs the command
   107  func (o *StepBootVaultOptions) Run() error {
   108  	ns, err := o.GetDeployNamespace(o.Namespace)
   109  	if err != nil {
   110  		return err
   111  	}
   112  
   113  	requirements, fileName, err := config.LoadRequirementsConfig(o.Dir, config.DefaultFailOnValidationError)
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	info := util.ColorInfo
   119  	if requirements.SecretStorage != config.SecretStorageTypeVault {
   120  		log.Logger().Infof("Not attempting to boot Vault as using secret storage: %s\n", info(string(requirements.SecretStorage)))
   121  		return nil
   122  	}
   123  
   124  	kubeClient, err := o.KubeClient()
   125  	if err != nil {
   126  		return errors.Wrapf(err, "failed to create Kubernetes client")
   127  	}
   128  
   129  	internal := requirements.Vault.URL == ""
   130  	// if we are not in batch mode and the key values in jx-requirements.yml are not set, interactively query the user
   131  	if !o.BatchMode && requirements.Vault.URL == "" && requirements.Vault.Name == "" {
   132  		internal, err = o.interactiveVaultConfiguration(internal, requirements, fileName)
   133  		if err != nil {
   134  			return errors.Wrapf(err, "failed to interactively configure Vault")
   135  		}
   136  	}
   137  
   138  	if internal {
   139  		return o.setupInClusterVault(requirements, ns, kubeClient)
   140  	}
   141  	return o.setupExternalVault(requirements, ns, kubeClient)
   142  }
   143  
   144  func (o *StepBootVaultOptions) interactiveVaultConfiguration(internal bool, requirements *config.RequirementsConfig, fileName string) (bool, error) {
   145  	help := "Jenkins X uses Vault to store secrets. You can provide your own Vault instance or let Jenkins X create one for you."
   146  	message := "Do you want Jenkins X to create and manage Vault?"
   147  	internal, err := util.Confirm(message, true, help, o.GetIOFileHandles())
   148  	if err != nil {
   149  		return false, errors.Wrapf(err, "unable to process user input")
   150  	}
   151  
   152  	if internal {
   153  		return true, nil
   154  	}
   155  
   156  	err = o.askExternalVaultParameters(requirements, o.GetIOFileHandles())
   157  	if err != nil {
   158  		return false, errors.Wrapf(err, "unable to ask user for Vault configuration")
   159  	}
   160  	err = requirements.SaveConfig(fileName)
   161  	if err != nil {
   162  		return false, errors.Wrap(err, "unable to write updated requirements file")
   163  	}
   164  	return false, nil
   165  }
   166  
   167  func (o *StepBootVaultOptions) askExternalVaultParameters(requirements *config.RequirementsConfig, fileHandles util.IOFileHandles) error {
   168  	url, err := util.PickValue("URL to Vault instance: ", "", true, "Please specify the URL to the Vault instance for storing your Jenkins X secrets", fileHandles)
   169  	if err != nil {
   170  		return errors.Wrap(err, "unable to get Vault URL from user")
   171  	}
   172  	requirements.Vault.URL = url
   173  
   174  	sa, err := util.PickValue("Authenticating service account: ", fmt.Sprintf("%s-vt", requirements.Cluster.ClusterName), true, "Please specify the service account used to authenticate against Vault", fileHandles)
   175  	if err != nil {
   176  		return errors.Wrap(err, "unable to get service account from user")
   177  	}
   178  	requirements.Vault.ServiceAccount = sa
   179  
   180  	ns, err := util.PickValue("Namespace of authenticating service account: ", requirements.Cluster.Namespace, true, "Please specify the namespace of the authenticating service account", fileHandles)
   181  	if err != nil {
   182  		return errors.Wrap(err, "unable to get namespace from user")
   183  	}
   184  	requirements.Vault.Namespace = ns
   185  
   186  	authPath, err := util.PickValue("Path under which to enable Vault's Kubernetes auth plugin: ", vault.DefaultKubernetesAuthPath, true, "Please specify the path for Vault's Kubernetes auth plugin. See https://www.vaultproject.io/docs/auth/kubernetes.", fileHandles)
   187  	if err != nil {
   188  		return errors.Wrap(err, "unable to get Kubernetes auth path from user")
   189  	}
   190  	requirements.Vault.KubernetesAuthPath = authPath
   191  
   192  	mountPoint, err := util.PickValue("Mount point for Vault's KV secret engine: ", vault.DefaultKVEngineMountPoint, true, "Please specify the mount point for Vault's KV secrets engine. See https://www.vaultproject.io/docs/secrets/kv", fileHandles)
   193  	if err != nil {
   194  		return errors.Wrap(err, "unable to get Vault URL from user")
   195  	}
   196  	requirements.Vault.SecretEngineMountPoint = mountPoint
   197  
   198  	return nil
   199  }
   200  
   201  func (o *StepBootVaultOptions) setupExternalVault(requirements *config.RequirementsConfig, ns string, kubeClient kubernetes.Interface) error {
   202  	namespace := requirements.Vault.Namespace
   203  	if namespace == "" {
   204  		namespace = ns
   205  	}
   206  	vault, err := vault.NewExternalVault(requirements.Vault.URL, requirements.Vault.ServiceAccount, namespace, requirements.Vault.SecretEngineMountPoint, requirements.Vault.KubernetesAuthPath)
   207  	if err != nil {
   208  		return errors.Wrapf(err, "invalid configuration for external Vault setup")
   209  	}
   210  
   211  	selector := NewVaultSelector(vault)
   212  	vaultFactory, err := kubevault.NewVaultClientFactoryWithSelector(kubeClient, selector, ns)
   213  	if err != nil {
   214  		return errors.Wrap(err, "unable to create Vault factory for external Vault instance")
   215  	}
   216  
   217  	_, err = vaultFactory.NewVaultClientForURL(vault, false)
   218  	if err != nil {
   219  		return errors.Wrap(err, "unable to create Vault client for external Vault instance")
   220  	}
   221  
   222  	err = o.storeExternalVaultConfig(kubeClient, ns, vault)
   223  	if err != nil {
   224  		return errors.Wrapf(err, "unable to store Vault configuration in ConfigMap '%s'", kube.ConfigMapNameJXInstallConfig)
   225  	}
   226  
   227  	log.Logger().Infof("Using external Vault instance %s - %s", vault.URL, util.ColorInfo("OK"))
   228  	return nil
   229  }
   230  
   231  func (o *StepBootVaultOptions) setupInClusterVault(requirements *config.RequirementsConfig, ns string, kubeClient kubernetes.Interface) error {
   232  	if requirements.Vault.Name == "" {
   233  		requirements.Vault.Name = kubevault.SystemVaultNameForCluster(requirements.Cluster.ClusterName)
   234  	}
   235  	log.Logger().Debugf("Using vault name '%s'", requirements.Vault.Name)
   236  
   237  	vault, err := vault.NewInternalVault(requirements.Vault.Name, requirements.Vault.ServiceAccount, ns)
   238  	if err != nil {
   239  		return errors.Wrapf(err, "invalid configuration for external Vault setup")
   240  	}
   241  
   242  	err = o.installOperator(requirements, ns)
   243  	if err != nil {
   244  		return errors.Wrapf(err, "unable to install Vault operator")
   245  	}
   246  
   247  	_, err = o.verifyVaultIngress(requirements, kubeClient, ns, requirements.Vault.Name)
   248  	if err != nil {
   249  		return err
   250  	}
   251  
   252  	err = o.storeInternalVaultConfig(kubeClient, vault, ns)
   253  	if err != nil {
   254  		return err
   255  	}
   256  
   257  	vaultOperatorClient, err := o.VaultOperatorClient()
   258  	if err != nil {
   259  		return errors.Wrap(err, "creating vault operator client")
   260  	}
   261  
   262  	resolver, err := o.CreateVersionResolver(requirements.VersionStream.URL, requirements.VersionStream.Ref)
   263  	if err != nil {
   264  		return errors.Wrap(err, "unable to create version stream resolver")
   265  	}
   266  
   267  	provider := requirements.Cluster.Provider
   268  
   269  	// only allow to make changes to cloud resources when running locally via `jx boot`
   270  	// when run in pipeline, the pipeline SA does not have permissions to create buckets, etc
   271  	// the assumption is that when the code runs in the pipeline all cloud resources already exist
   272  	// (`jx boot` has been executed once at least)
   273  	createCloudResources := o.IsJXBoot()
   274  
   275  	vaultCreateParam := create.VaultCreationParam{
   276  		VaultName:            requirements.Vault.Name,
   277  		Namespace:            ns,
   278  		ClusterName:          requirements.Cluster.ClusterName,
   279  		ServiceAccountName:   requirements.Vault.ServiceAccount,
   280  		SecretsPathPrefix:    pkgvault.DefaultSecretsPathPrefix,
   281  		KubeProvider:         provider,
   282  		KubeClient:           kubeClient,
   283  		VaultOperatorClient:  vaultOperatorClient,
   284  		VersionResolver:      *resolver,
   285  		FileHandles:          o.GetIOFileHandles(),
   286  		CreateCloudResources: createCloudResources,
   287  		Boot:                 true,
   288  		BatchMode:            true,
   289  	}
   290  
   291  	if provider == cloud.GKE {
   292  		gkeParam := &create.GKEParam{
   293  			ProjectID:      gkevault.GetGoogleProjectID(kubeClient, ns),
   294  			Zone:           gkevault.GetGoogleZone(kubeClient, ns),
   295  			BucketName:     requirements.Vault.Bucket,
   296  			KeyringName:    requirements.Vault.Keyring,
   297  			KeyName:        requirements.Vault.Key,
   298  			RecreateBucket: requirements.Vault.RecreateBucket,
   299  		}
   300  		vaultCreateParam.GKE = gkeParam
   301  	} else if provider == cloud.EKS {
   302  		awsParam, err := o.createAWSParam(requirements)
   303  		if err != nil {
   304  			return errors.Wrap(err, "unable to create Vault creation parameter from requirements")
   305  		}
   306  		vaultCreateParam.AWS = &awsParam
   307  	} else if provider == cloud.AKS {
   308  		azureParam, err := o.createAzureParam(requirements)
   309  		if err != nil {
   310  			return errors.Wrap(err, "unable to create Vault creation parameter from requirements")
   311  		}
   312  		vaultCreateParam.Azure = &azureParam
   313  	}
   314  
   315  	vaultCreator := create.NewVaultCreator()
   316  	err = vaultCreator.CreateOrUpdateVault(vaultCreateParam)
   317  	if err != nil {
   318  		return errors.Wrap(err, "unable to create/update Vault")
   319  	}
   320  	return nil
   321  }
   322  
   323  func (o *StepBootVaultOptions) createAWSParam(requirements *config.RequirementsConfig) (create.AWSParam, error) {
   324  	if requirements.Vault.AWSConfig == nil {
   325  		return create.AWSParam{}, errors.New("missing AWS configuration for Vault in requirements")
   326  	}
   327  
   328  	awsConfig := requirements.Vault.AWSConfig
   329  	secretAccessKey := os.Getenv("VAULT_AWS_SECRET_ACCESS_KEY")
   330  	accessKeyID := os.Getenv("VAULT_AWS_ACCESS_KEY_ID")
   331  	if !awsConfig.AutoCreate && (checkRequiredResource("dynamoDBTable", awsConfig.DynamoDBTable) ||
   332  		checkRequiredResource("secretAccessKey", secretAccessKey) ||
   333  		checkRequiredResource("accessKeyID", accessKeyID) ||
   334  		checkRequiredResource("kmsKeyId", awsConfig.KMSKeyID) ||
   335  		checkRequiredResource("s3Bucket", awsConfig.S3Bucket)) {
   336  		log.Logger().Info("Some of the required provided values are empty - We will create all resources")
   337  		awsConfig.AutoCreate = true
   338  	}
   339  
   340  	templatesDir := filepath.Join(o.Dir, o.ProviderValuesDir, cloud.EKS, "templates")
   341  
   342  	defaultRegion := requirements.Cluster.Region
   343  	if defaultRegion == "" {
   344  		return create.AWSParam{}, errors.New("unable to find cluster region in requirements")
   345  	}
   346  
   347  	dynamoDBRegion := awsConfig.DynamoDBRegion
   348  	if dynamoDBRegion == "" {
   349  		dynamoDBRegion = defaultRegion
   350  		log.Logger().Infof("Region not specified for DynamoDB, defaulting to %s", util.ColorInfo(defaultRegion))
   351  	}
   352  
   353  	kmsRegion := awsConfig.KMSRegion
   354  	if kmsRegion == "" {
   355  		kmsRegion = defaultRegion
   356  		log.Logger().Infof("Region not specified for KMS, defaulting to %s", util.ColorInfo(defaultRegion))
   357  
   358  	}
   359  
   360  	s3Region := awsConfig.S3Region
   361  	if s3Region == "" {
   362  		s3Region = defaultRegion
   363  		log.Logger().Infof("Region not specified for S3, defaulting to %s", util.ColorInfo(defaultRegion))
   364  	}
   365  
   366  	awsParam := create.AWSParam{
   367  		IAMUsername:     awsConfig.ProvidedIAMUsername,
   368  		S3Bucket:        awsConfig.S3Bucket,
   369  		S3Region:        s3Region,
   370  		S3Prefix:        awsConfig.S3Prefix,
   371  		TemplatesDir:    templatesDir,
   372  		DynamoDBTable:   awsConfig.DynamoDBTable,
   373  		DynamoDBRegion:  dynamoDBRegion,
   374  		KMSKeyID:        awsConfig.KMSKeyID,
   375  		KMSRegion:       kmsRegion,
   376  		AccessKeyID:     accessKeyID,
   377  		SecretAccessKey: secretAccessKey,
   378  		AutoCreate:      awsConfig.AutoCreate,
   379  	}
   380  
   381  	return awsParam, nil
   382  }
   383  
   384  func (o *StepBootVaultOptions) createAzureParam(requirements *config.RequirementsConfig) (create.AzureParam, error) {
   385  	if requirements.Vault.AzureConfig == nil {
   386  		return create.AzureParam{}, errors.New("missing Azure configuration for Vault in requirements")
   387  	}
   388  
   389  	azureConfig := requirements.Vault.AzureConfig
   390  	storageAccessKey := os.Getenv("VAULT_AZURE_STORAGE_ACCESS_KEY")
   391  
   392  	azureParam := create.AzureParam{
   393  		TenantID:           azureConfig.TenantID,
   394  		StorageAccountKey:  storageAccessKey,
   395  		StorageAccountName: azureConfig.StorageAccountName,
   396  		ContainerName:      azureConfig.ContainerName,
   397  		KeyName:            azureConfig.KeyName,
   398  		VaultName:          azureConfig.VaultName,
   399  	}
   400  
   401  	return azureParam, nil
   402  }
   403  
   404  func (o *StepBootVaultOptions) storeInternalVaultConfig(kubeClient kubernetes.Interface, vaultConfig vault.Vault, ns string) error {
   405  	_, err := kube.DefaultModifyConfigMap(kubeClient, ns, kube.ConfigMapNameJXInstallConfig,
   406  		func(configMap *corev1.ConfigMap) error {
   407  			configMap.Data[secrets.SecretsLocationKey] = string(secrets.VaultLocationKind)
   408  
   409  			vaultConfig := vaultConfig.ToMap()
   410  			configMap.Data = util.MergeMaps(configMap.Data, vaultConfig)
   411  
   412  			return nil
   413  		}, nil)
   414  	if err != nil {
   415  		return errors.Wrapf(err, "error saving system vault name in ConfigMap %s in namespace %s", kube.ConfigMapNameJXInstallConfig, ns)
   416  	}
   417  	return nil
   418  }
   419  
   420  func (o *StepBootVaultOptions) storeExternalVaultConfig(kubeClient kubernetes.Interface, ns string, vaultConfig vault.Vault) error {
   421  	_, err := kube.DefaultModifyConfigMap(kubeClient, ns, kube.ConfigMapNameJXInstallConfig,
   422  		func(configMap *corev1.ConfigMap) error {
   423  			configMap.Data[secrets.SecretsLocationKey] = string(secrets.VaultLocationKind)
   424  
   425  			vaultConfig := vaultConfig.ToMap()
   426  			configMap.Data = util.MergeMaps(configMap.Data, vaultConfig)
   427  
   428  			return nil
   429  		}, nil)
   430  	if err != nil {
   431  		return errors.Wrapf(err, "error saving external Vault configuration in ConfigMap %s in namespace %s", kube.ConfigMapNameJXInstallConfig, ns)
   432  	}
   433  	return nil
   434  }
   435  
   436  func (o *StepBootVaultOptions) installOperator(requirements *config.RequirementsConfig, ns string) error {
   437  	tag, err := o.vaultOperatorImageTag(&requirements.VersionStream)
   438  	if err != nil {
   439  		return errors.Wrap(err, "unable to determine Vault operator version")
   440  	}
   441  
   442  	releaseName := o.ReleaseName
   443  	if releaseName == "" {
   444  		releaseName = kube.DefaultVaultOperatorReleaseName
   445  	}
   446  
   447  	values := []string{
   448  		"image.repository=" + kubevault.VaultOperatorImage,
   449  		"image.tag=" + tag,
   450  	}
   451  	log.Logger().Infof("Installing %s operator with helm values: %v", util.ColorInfo(releaseName), util.ColorInfo(values))
   452  
   453  	helmOptions := helm.InstallChartOptions{
   454  		Chart:       kube.ChartVaultOperator,
   455  		ReleaseName: releaseName,
   456  		Version:     o.Version,
   457  		Ns:          ns,
   458  		SetValues:   values,
   459  	}
   460  	err = o.InstallChartWithOptions(helmOptions)
   461  	if err != nil {
   462  		return errors.Wrap(err, "unable to install vault operator")
   463  	}
   464  
   465  	log.Logger().Infof("Vault operator installed in namespace %s", ns)
   466  	return nil
   467  }
   468  
   469  // verifyVaultIngress verifies there is a Vault ingress and if not create one if there is a file at
   470  func (o *StepBootVaultOptions) verifyVaultIngress(requirements *config.RequirementsConfig, kubeClient kubernetes.Interface, ns string, systemVaultName string) (bool, error) {
   471  	fileName := filepath.Join(o.Dir, "vault-ing.tmpl.yaml")
   472  	exists, err := util.FileExists(fileName)
   473  	if err != nil {
   474  		return false, errors.Wrapf(err, "failed to check if file exists %s", fileName)
   475  	}
   476  	if !exists {
   477  		log.Logger().Warnf("failed to find file %s\n", fileName)
   478  		return false, nil
   479  	}
   480  	data, err := readYamlTemplate(fileName, requirements)
   481  	if err != nil {
   482  		return true, errors.Wrapf(err, "failed to load vault ingress template file %s", fileName)
   483  	}
   484  
   485  	log.Logger().Infof("Applying vault ingress in namespace %s for vault name %s\n", util.ColorInfo(ns), util.ColorInfo(systemVaultName))
   486  
   487  	tmpFile, err := ioutil.TempFile("", "vault-ingress-")
   488  	if err != nil {
   489  		return true, errors.Wrapf(err, "failed to create temporary file for vault YAML")
   490  	}
   491  
   492  	tmpFileName := tmpFile.Name()
   493  	err = ioutil.WriteFile(tmpFileName, data, util.DefaultWritePermissions)
   494  	if err != nil {
   495  		return true, errors.Wrapf(err, "failed to save vault ingress YAML file %s", tmpFileName)
   496  	}
   497  
   498  	args := []string{"apply", "--force", "-f", tmpFileName, "-n", ns}
   499  	err = o.RunCommand("kubectl", args...)
   500  	if err != nil {
   501  		return true, errors.Wrapf(err, "failed to apply vault ingress YAML")
   502  	}
   503  	return true, nil
   504  }
   505  
   506  // vaultOperatorImageTag lookups the vault operator image tag in the version stream
   507  func (o *StepBootVaultOptions) vaultOperatorImageTag(versionStream *config.VersionStreamConfig) (string, error) {
   508  	resolver, err := o.CreateVersionResolver(versionStream.URL, versionStream.Ref)
   509  	if err != nil {
   510  		return "", errors.Wrap(err, "creating the vault-operator docker image version resolver")
   511  	}
   512  	fullImage, err := resolver.ResolveDockerImage(kubevault.VaultOperatorImage)
   513  	if err != nil {
   514  		return "", errors.Wrapf(err, "looking up the vault-operator %q image into the version stream",
   515  			kubevault.VaultOperatorImage)
   516  	}
   517  	parts := strings.Split(fullImage, ":")
   518  	if len(parts) != 2 {
   519  		return "", fmt.Errorf("no tag found for image %q in version stream", kubevault.VaultOperatorImage)
   520  	}
   521  	return parts[1], nil
   522  }
   523  
   524  // readYamlTemplate evaluates the given go template file and returns the output data
   525  func readYamlTemplate(templateFile string, requirements *config.RequirementsConfig) ([]byte, error) {
   526  	_, name := filepath.Split(templateFile)
   527  	funcMap := helm.NewFunctionMap()
   528  	tmpl, err := template.New(name).Option("missingkey=error").Funcs(funcMap).ParseFiles(templateFile)
   529  	if err != nil {
   530  		return nil, errors.Wrapf(err, "failed to parse Ingress template: %s", templateFile)
   531  	}
   532  
   533  	requirementsMap, err := requirements.ToMap()
   534  	if err != nil {
   535  		return nil, errors.Wrapf(err, "failed turn requirements into a map: %v", requirements)
   536  	}
   537  
   538  	templateData := map[string]interface{}{
   539  		"Requirements": chartutil.Values(requirementsMap),
   540  		"Environments": chartutil.Values(requirements.EnvironmentMap()),
   541  	}
   542  	var buf bytes.Buffer
   543  	err = tmpl.Execute(&buf, templateData)
   544  	if err != nil {
   545  		return nil, errors.Wrapf(err, "failed to execute Ingress template: %s", templateFile)
   546  	}
   547  	data := buf.Bytes()
   548  	return data, nil
   549  }
   550  
   551  func checkRequiredResource(resourceName string, value string) bool {
   552  	if value == "" {
   553  		log.Logger().Warnf("Vault.AutoCreate is false but required property %s is missing", resourceName)
   554  		return true
   555  	}
   556  	return false
   557  }