github.com/jenkins-x/jx/v2@v2.1.155/pkg/cmd/step/git/credentials/step_git_credentials.go (about)

     1  package credentials
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  
    10  	"github.com/jenkins-x/jx/v2/pkg/auth"
    11  	"github.com/jenkins-x/jx/v2/pkg/cmd/opts/step"
    12  	"github.com/jenkins-x/jx/v2/pkg/gits/credentialhelper"
    13  	"github.com/pkg/errors"
    14  
    15  	"github.com/jenkins-x/jx/v2/pkg/cmd/helper"
    16  
    17  	"github.com/jenkins-x/jx-logging/pkg/log"
    18  	"github.com/jenkins-x/jx/v2/pkg/cmd/opts"
    19  	"github.com/jenkins-x/jx/v2/pkg/cmd/templates"
    20  	"github.com/jenkins-x/jx/v2/pkg/util"
    21  	"github.com/spf13/cobra"
    22  
    23  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  )
    26  
    27  const (
    28  	optionOutputFile = "output"
    29  	// Deprecated
    30  	optionGitHubAppOwner = "github-app-owner"
    31  	optionRepoOwner      = "repo-owner"
    32  )
    33  
    34  // StepGitCredentialsOptions contains the command line flags
    35  type StepGitCredentialsOptions struct {
    36  	step.StepOptions
    37  
    38  	OutputFile string
    39  	// Deprecated
    40  	GitHubAppOwner    string
    41  	RepoOwner         string
    42  	GitKind           string
    43  	CredentialsSecret string
    44  	CredentialHelper  bool
    45  }
    46  
    47  var (
    48  	StepGitCredentialsLong = templates.LongDesc(`
    49  		This pipeline step generates a Git credentials file for the current Git provider secrets
    50  
    51  `)
    52  
    53  	StepGitCredentialsExample = templates.Examples(`
    54  		# generate the Git credentials file in the canonical location
    55  		jx step git credentials
    56  
    57  		# generate the Git credentials to a output file
    58  		jx step git credentials -o /tmp/mycreds
    59  
    60  		# respond to a gitcredentials request
    61  		jx step git credentials --credential-helper
    62  `)
    63  )
    64  
    65  func NewCmdStepGitCredentials(commonOpts *opts.CommonOptions) *cobra.Command {
    66  	options := StepGitCredentialsOptions{
    67  		StepOptions: step.StepOptions{
    68  			CommonOptions: commonOpts,
    69  		},
    70  	}
    71  	cmd := &cobra.Command{
    72  		Use:     "credentials",
    73  		Short:   "Creates the Git credentials file for the current pipeline",
    74  		Long:    StepGitCredentialsLong,
    75  		Example: StepGitCredentialsExample,
    76  		Run: func(cmd *cobra.Command, args []string) {
    77  			options.Cmd = cmd
    78  			options.Args = args
    79  			err := options.Run()
    80  			helper.CheckErr(err)
    81  		},
    82  	}
    83  	cmd.Flags().StringVarP(&options.OutputFile, optionOutputFile, "o", "", "The output file name")
    84  	cmd.Flags().StringVarP(&options.GitHubAppOwner, optionGitHubAppOwner, "g", "", "Deprecated - The owner (organisation or user name) if using GitHub App based tokens")
    85  	cmd.Flags().StringVarP(&options.RepoOwner, optionRepoOwner, "r", "", "The owner (organisation or user name) if using GitHub App based tokens")
    86  
    87  	cmd.Flags().StringVarP(&options.CredentialsSecret, "credentials-secret", "s", "", "The secret name to read the credentials from")
    88  	cmd.Flags().StringVarP(&options.GitKind, "git-kind", "", "", "The git kind. e.g. github, bitbucketserver etc")
    89  	cmd.Flags().BoolVar(&options.CredentialHelper, "credential-helper", false, "respond to a gitcredentials request")
    90  
    91  	return cmd
    92  }
    93  
    94  func (o *StepGitCredentialsOptions) Run() error {
    95  	if os.Getenv("JX_CREDENTIALS_FROM_SECRET") != "" {
    96  		log.Logger().Infof("Overriding CredentialsSecret from env var JX_CREDENTIALS_FROM_SECRET")
    97  		o.CredentialsSecret = os.Getenv("JX_CREDENTIALS_FROM_SECRET")
    98  	}
    99  
   100  	outFile, err := o.determineOutputFile()
   101  	if err != nil {
   102  		return err
   103  	}
   104  
   105  	if o.CredentialsSecret != "" {
   106  		// get secret
   107  		kubeClient, ns, err := o.KubeClientAndDevNamespace()
   108  		if err != nil {
   109  			return err
   110  		}
   111  
   112  		secret, err := kubeClient.CoreV1().Secrets(ns).Get(o.CredentialsSecret, metav1.GetOptions{})
   113  		if err != nil {
   114  			if apierrors.IsNotFound(err) {
   115  				return errors.Wrapf(err, "failed to find secret '%s' in namespace '%s'", o.CredentialsSecret, ns)
   116  			}
   117  			return errors.Wrapf(err, "failed to read secret '%s' in namespace '%s'", o.CredentialsSecret, ns)
   118  		}
   119  
   120  		creds, err := credentialhelper.CreateGitCredentialFromURL(string(secret.Data["url"]), string(secret.Data["token"]), string(secret.Data["user"]))
   121  		if err != nil {
   122  			return errors.Wrap(err, "failed to create git credentials")
   123  		}
   124  
   125  		return o.createGitCredentialsFile(outFile, []credentialhelper.GitCredential{creds})
   126  	}
   127  
   128  	gha, err := o.IsGitHubAppMode()
   129  	if err != nil {
   130  		return err
   131  	}
   132  
   133  	if o.RepoOwner == "" && o.GitHubAppOwner != "" {
   134  		log.Logger().Warnf("The flag --%s is deprecated, use --%s instead", optionGitHubAppOwner, optionRepoOwner)
   135  		o.RepoOwner = o.GitHubAppOwner
   136  	}
   137  
   138  	if gha && o.RepoOwner == "" {
   139  		log.Logger().Infof("this command does nothing if using github app mode and no %s option specified", optionRepoOwner)
   140  		return nil
   141  	}
   142  
   143  	var authConfigSvc auth.ConfigService
   144  	if gha {
   145  		authConfigSvc, err = o.GitAuthConfigServiceGitHubAppMode(o.GitKind)
   146  		if err != nil {
   147  			return errors.Wrap(err, "when creating auth config service using GitAuthConfigServiceGitHubAppMode")
   148  		}
   149  	} else {
   150  		authConfigSvc, err = o.GitAuthConfigService()
   151  		if err != nil {
   152  			return errors.Wrap(err, "when creating auth config service using GitAuthConfigService")
   153  		}
   154  	}
   155  
   156  	credentials, err := o.CreateGitCredentialsFromAuthService(authConfigSvc, gha)
   157  	if err != nil {
   158  		return errors.Wrap(err, "creating git credentials")
   159  	}
   160  
   161  	if o.CredentialHelper {
   162  		helper, err := credentialhelper.CreateGitCredentialsHelper(os.Stdin, os.Stdout, credentials)
   163  		if err != nil {
   164  			return errors.Wrap(err, "unable to create git credential helper")
   165  		}
   166  		// the credential helper operation (get|store|remove) is passed as last argument to the helper
   167  		err = helper.Run(os.Args[len(os.Args)-1])
   168  		if err != nil {
   169  			return err
   170  		}
   171  		return nil
   172  	}
   173  
   174  	outFile, err = o.determineOutputFile()
   175  	if err != nil {
   176  		return errors.Wrap(err, "unable to determine for git credentials")
   177  	}
   178  
   179  	return o.createGitCredentialsFile(outFile, credentials)
   180  }
   181  
   182  // GitCredentialsFileData takes the given git credentials and writes them into a byte array.
   183  func (o *StepGitCredentialsOptions) GitCredentialsFileData(credentials []credentialhelper.GitCredential) ([]byte, error) {
   184  	var buffer bytes.Buffer
   185  	for _, gitCredential := range credentials {
   186  		u, err := gitCredential.URL()
   187  		if err != nil {
   188  			log.Logger().Warnf("Ignoring incomplete git credentials %q", gitCredential)
   189  			continue
   190  		}
   191  
   192  		buffer.WriteString(u.String() + "\n")
   193  		// Write the https protocol in case only https is set for completeness
   194  		if u.Scheme == "http" {
   195  			u.Scheme = "https"
   196  			buffer.WriteString(u.String() + "\n")
   197  		}
   198  	}
   199  
   200  	return buffer.Bytes(), nil
   201  }
   202  
   203  func (o *StepGitCredentialsOptions) determineOutputFile() (string, error) {
   204  	outFile := o.OutputFile
   205  	if outFile == "" {
   206  		outFile = util.GitCredentialsFile()
   207  	}
   208  
   209  	dir, _ := filepath.Split(outFile)
   210  	if dir != "" {
   211  		err := os.MkdirAll(dir, util.DefaultWritePermissions)
   212  		if err != nil {
   213  			return "", err
   214  		}
   215  	}
   216  	return outFile, nil
   217  }
   218  
   219  // CreateGitCredentialsFileFromUsernameAndToken creates the git credentials into file using the provided username, token & url
   220  func (o *StepGitCredentialsOptions) createGitCredentialsFile(fileName string, credentials []credentialhelper.GitCredential) error {
   221  	data, err := o.GitCredentialsFileData(credentials)
   222  	if err != nil {
   223  		return errors.Wrap(err, "creating git credentials")
   224  	}
   225  
   226  	if err := ioutil.WriteFile(fileName, data, util.DefaultWritePermissions); err != nil {
   227  		return fmt.Errorf("failed to write to %s: %s", fileName, err)
   228  	}
   229  	log.Logger().Infof("Generated Git credentials file %s", util.ColorInfo(fileName))
   230  	return nil
   231  }
   232  
   233  // CreateGitCredentialsFromAuthService creates the git credentials using the auth config service
   234  func (o *StepGitCredentialsOptions) CreateGitCredentialsFromAuthService(authConfigSvc auth.ConfigService, githubAppEnabled bool) ([]credentialhelper.GitCredential, error) {
   235  	var credentialList []credentialhelper.GitCredential
   236  
   237  	cfg := authConfigSvc.Config()
   238  	if cfg == nil {
   239  		return nil, errors.New("no git auth config found")
   240  	}
   241  
   242  	for _, server := range cfg.Servers {
   243  		var auths []*auth.UserAuth
   244  		if githubAppEnabled && o.RepoOwner != "" {
   245  			auths = server.Users
   246  		} else {
   247  			gitAuth := server.CurrentAuth()
   248  			if gitAuth == nil {
   249  				continue
   250  			} else {
   251  				auths = append(auths, gitAuth)
   252  			}
   253  		}
   254  		for _, gitAuth := range auths {
   255  			if githubAppEnabled && o.RepoOwner != "" && gitAuth.GithubAppOwner != o.RepoOwner {
   256  				continue
   257  			}
   258  			username := gitAuth.Username
   259  			password := gitAuth.ApiToken
   260  			if password == "" {
   261  				password = gitAuth.BearerToken
   262  			}
   263  			if password == "" {
   264  				password = gitAuth.Password
   265  			}
   266  			if username == "" || password == "" {
   267  				log.Logger().Warnf("Empty auth config for git service URL %q", server.URL)
   268  				continue
   269  			}
   270  
   271  			credential, err := credentialhelper.CreateGitCredentialFromURL(server.URL, username, password)
   272  			if err != nil {
   273  				return nil, errors.Wrapf(err, "invalid git auth information")
   274  			}
   275  
   276  			credentialList = append(credentialList, credential)
   277  		}
   278  	}
   279  	return credentialList, nil
   280  }