github.com/jfrog/jfrog-cli-core@v1.12.1/artifactory/commands/golang/go.go (about)

     1  package golang
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"path"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/jfrog/gocmd"
    11  	executersutils "github.com/jfrog/gocmd/executers/utils"
    12  	"github.com/jfrog/gocmd/params"
    13  	"github.com/jfrog/jfrog-cli-core/artifactory/utils"
    14  	"github.com/jfrog/jfrog-cli-core/artifactory/utils/golang"
    15  	"github.com/jfrog/jfrog-cli-core/artifactory/utils/golang/project"
    16  	"github.com/jfrog/jfrog-cli-core/utils/config"
    17  	"github.com/jfrog/jfrog-client-go/artifactory"
    18  	_go "github.com/jfrog/jfrog-client-go/artifactory/services/go"
    19  	"github.com/jfrog/jfrog-client-go/auth"
    20  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
    21  	"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
    22  	"github.com/jfrog/jfrog-client-go/utils/version"
    23  )
    24  
    25  const GoCommandName = "rt_go"
    26  
    27  type GoCommand struct {
    28  	noRegistry         bool
    29  	publishDeps        bool
    30  	goArg              []string
    31  	buildConfiguration *utils.BuildConfiguration
    32  	deployerParams     *utils.RepositoryConfig
    33  	resolverParams     *utils.RepositoryConfig
    34  }
    35  
    36  func NewGoCommand() *GoCommand {
    37  	return &GoCommand{}
    38  }
    39  
    40  func (gc *GoCommand) SetResolverParams(resolverParams *utils.RepositoryConfig) *GoCommand {
    41  	gc.resolverParams = resolverParams
    42  	return gc
    43  }
    44  
    45  func (gc *GoCommand) SetDeployerParams(deployerParams *utils.RepositoryConfig) *GoCommand {
    46  	gc.deployerParams = deployerParams
    47  	return gc
    48  }
    49  
    50  func (gc *GoCommand) SetBuildConfiguration(buildConfiguration *utils.BuildConfiguration) *GoCommand {
    51  	gc.buildConfiguration = buildConfiguration
    52  	return gc
    53  }
    54  
    55  func (gc *GoCommand) SetNoRegistry(noRegistry bool) *GoCommand {
    56  	gc.noRegistry = noRegistry
    57  	return gc
    58  }
    59  
    60  func (gc *GoCommand) SetPublishDeps(publishDeps bool) *GoCommand {
    61  	gc.publishDeps = publishDeps
    62  	return gc
    63  }
    64  
    65  func (gc *GoCommand) SetGoArg(goArg []string) *GoCommand {
    66  	gc.goArg = goArg
    67  	return gc
    68  }
    69  
    70  func (gc *GoCommand) ServerDetails() (*config.ServerDetails, error) {
    71  	if gc.deployerParams != nil && !gc.deployerParams.IsServerDetailsEmpty() {
    72  		return gc.deployerParams.ServerDetails()
    73  	}
    74  	return gc.resolverParams.ServerDetails()
    75  }
    76  
    77  func (gc *GoCommand) CommandName() string {
    78  	return GoCommandName
    79  }
    80  
    81  func (gc *GoCommand) Run() error {
    82  	err := golang.LogGoVersion()
    83  	if err != nil {
    84  		return err
    85  	}
    86  	buildName := gc.buildConfiguration.BuildName
    87  	buildNumber := gc.buildConfiguration.BuildNumber
    88  	projectKey := gc.buildConfiguration.Project
    89  	isCollectBuildInfo := len(buildName) > 0 && len(buildNumber) > 0
    90  	if isCollectBuildInfo {
    91  		err = utils.SaveBuildGeneralDetails(buildName, buildNumber, projectKey)
    92  		if err != nil {
    93  			return err
    94  		}
    95  	}
    96  
    97  	resolverDetails, err := gc.resolverParams.ServerDetails()
    98  	if err != nil {
    99  		return err
   100  	}
   101  	resolverServiceManager, err := utils.CreateServiceManager(resolverDetails, -1, false)
   102  	if err != nil {
   103  		return err
   104  	}
   105  	resolverParams := &params.Params{}
   106  	resolverParams.SetRepo(gc.resolverParams.TargetRepo()).SetServiceManager(resolverServiceManager)
   107  	goInfo := &params.ResolverDeployer{}
   108  	goInfo.SetResolver(resolverParams)
   109  	var targetRepo string
   110  	var deployerServiceManager artifactory.ArtifactoryServicesManager
   111  	if gc.publishDeps {
   112  		deployerDetails, err := gc.deployerParams.ServerDetails()
   113  		if err != nil {
   114  			return err
   115  		}
   116  		deployerServiceManager, err = utils.CreateServiceManager(deployerDetails, -1, false)
   117  		if err != nil {
   118  			return err
   119  		}
   120  		targetRepo = gc.deployerParams.TargetRepo()
   121  		deployerParams := &params.Params{}
   122  		deployerParams.SetRepo(targetRepo).SetServiceManager(deployerServiceManager)
   123  		goInfo.SetDeployer(deployerParams)
   124  	}
   125  
   126  	err = gocmd.RunWithFallbacksAndPublish(gc.goArg, gc.noRegistry, gc.publishDeps, goInfo)
   127  	if err != nil {
   128  		return err
   129  	}
   130  	if isCollectBuildInfo {
   131  		tempDirPath := ""
   132  		if isGoGetCommand := len(gc.goArg) > 0 && gc.goArg[0] == "get"; isGoGetCommand {
   133  			if len(gc.goArg) < 2 {
   134  				// Package name was not supplied. Invalid go get commend
   135  				return errorutils.CheckError(errors.New("Invalid get command. Package name is missing"))
   136  			}
   137  			tempDirPath, err = fileutils.CreateTempDir()
   138  			if err != nil {
   139  				return err
   140  			}
   141  			// Cleanup the temp working directory at the end.
   142  			defer fileutils.RemoveTempDir(tempDirPath)
   143  			// Get Artifactory config in order to extract the package version.
   144  			serverDetails, err := resolverDetails.CreateArtAuthConfig()
   145  			if err != nil {
   146  				return err
   147  			}
   148  			err = copyGoPackageFiles(tempDirPath, gc.goArg[1], gc.resolverParams.TargetRepo(), serverDetails)
   149  			if err != nil {
   150  				return err
   151  			}
   152  		}
   153  		goProject, err := project.Load("-", tempDirPath)
   154  		if err != nil {
   155  			return err
   156  		}
   157  		includeInfoFiles, err := shouldIncludeInfoFiles(deployerServiceManager, resolverServiceManager)
   158  		if err != nil {
   159  			return err
   160  		}
   161  		err = goProject.LoadDependencies()
   162  		if err != nil {
   163  			return err
   164  		}
   165  		err = goProject.CreateBuildInfoDependencies(includeInfoFiles)
   166  		if err != nil {
   167  			return err
   168  		}
   169  		err = utils.SaveBuildInfo(buildName, buildNumber, projectKey, goProject.BuildInfo(false, gc.buildConfiguration.Module, targetRepo))
   170  	}
   171  
   172  	return err
   173  }
   174  
   175  // copyGoPackageFiles copies the package files from the go mod cache directory to the given destPath.
   176  // The path to those chache files is retrived using the supplied package name and Artifactory details.
   177  func copyGoPackageFiles(destPath, packageName, rtTargetRepo string, authArtDetails auth.ServiceDetails) error {
   178  	packageFilesPath, err := getPackageFilePathFromArtifactory(packageName, rtTargetRepo, authArtDetails)
   179  	if err != nil {
   180  		return err
   181  	}
   182  	// Copy the entire content of the relevant Go pkg directory to the requested destination path.
   183  	err = fileutils.CopyDir(packageFilesPath, destPath, true, nil)
   184  	if err != nil {
   185  		return fmt.Errorf("Couldn't find suitable package files: %s", packageFilesPath)
   186  	}
   187  	return nil
   188  }
   189  
   190  // getPackageFilePathFromArtifactory returns a string that represents the package files chache path.
   191  // In most cases the path to those chache files is retrived using the supplied package name and Artifactory details.
   192  // However if the user asked for a specifc version (package@vX.Y.Z) the unnecessary call to Artifactpry is avoided.
   193  func getPackageFilePathFromArtifactory(packageName, rtTargetRepo string, authArtDetails auth.ServiceDetails) (packageFilesPath string, err error) {
   194  	var version string
   195  	packageCachePath, err := executersutils.GetGoModCachePath()
   196  	if err != nil {
   197  		return
   198  	}
   199  	packageNameSplitted := strings.Split(packageName, "@")
   200  	name := packageNameSplitted[0]
   201  	// The case the user asks for a specifc version
   202  	if len(packageNameSplitted) == 2 && strings.HasPrefix(packageNameSplitted[1], "v") {
   203  		version = packageNameSplitted[1]
   204  	} else {
   205  		branchName := ""
   206  		// The case the user asks for a specifc branch
   207  		if len(packageNameSplitted) == 2 {
   208  			branchName = packageNameSplitted[1]
   209  		}
   210  		packageVersionRequest := buildPackageVersionRequest(name, branchName)
   211  		// Retrive the package version using Artifactory
   212  		version, err = executersutils.GetPackageVersion(rtTargetRepo, packageVersionRequest, authArtDetails)
   213  		if err != nil {
   214  			return
   215  		}
   216  	}
   217  	path, err := getFileSystemPackagePath(packageCachePath, name, version)
   218  	if err != nil {
   219  		return "", err
   220  	}
   221  	return path, nil
   222  
   223  }
   224  
   225  // getFileSystemPackagePath returns a string that represents the package files cache path.
   226  // In some cases when the path isn't represented by the package name, instead the name represents a specific project's directory's path.
   227  // In this case we will scan the path until we find the package directory.
   228  // Example : When running 'go get github.com/golang/mock/mockgen@v1.4.1'
   229  //			* "mockgen" is a directory inside "mock" package ("mockgen" doesn't contain "go.mod").
   230  //			* go download and save the whole "mock" package in local cache under 'github.com/golang/mock@v1.4.1' -- >
   231  //			  "go get" downloads and saves the whole "mock" package in the local cache under 'github.com/golang/mock@v1.4.1'
   232  func getFileSystemPackagePath(packageCachePath, name, version string) (string, error) {
   233  	separator := string(filepath.Separator)
   234  	// For Windows OS
   235  	path := filepath.Join(name)
   236  	for path != "" {
   237  		packagePath := filepath.Join(packageCachePath, path+"@"+version)
   238  		exists, err := fileutils.IsDirExists(packagePath, false)
   239  		if err != nil {
   240  			return "", err
   241  		}
   242  		if exists {
   243  			return packagePath, nil
   244  		}
   245  		// Remove path's last element and check again
   246  		path, _ = filepath.Split(path)
   247  		path = strings.TrimSuffix(path, separator)
   248  	}
   249  	return "", errors.New("Could not find package:" + name + " in:" + packageCachePath)
   250  }
   251  
   252  // buildPackageVersionRequest returns a string representing the version request to Artifactory.
   253  // The resulted string is in the following format: "<Package Name>/@V/<Branch Name>.info".
   254  // If a branch name is not given, the branch name will be replaced with the "latest" keyword.
   255  // ("<Package Name>/@V/latest.info").
   256  func buildPackageVersionRequest(name, branchName string) string {
   257  	packageVersionRequest := path.Join(name, "@v")
   258  	if branchName != "" {
   259  		// A branch name was given by the user
   260  		return path.Join(packageVersionRequest, branchName+".info")
   261  	}
   262  	// No version was given to "go get" command, so the latest version should be requested
   263  	return path.Join(packageVersionRequest, "latest.info")
   264  }
   265  
   266  // Returns true/false if info files should be included in the build info.
   267  func shouldIncludeInfoFiles(deployerServiceManager artifactory.ArtifactoryServicesManager, resolverServiceManager artifactory.ArtifactoryServicesManager) (bool, error) {
   268  	var artifactoryVersion string
   269  	var err error
   270  	if deployerServiceManager != nil {
   271  		artifactoryVersion, err = deployerServiceManager.GetConfig().GetServiceDetails().GetVersion()
   272  	} else {
   273  		artifactoryVersion, err = resolverServiceManager.GetConfig().GetServiceDetails().GetVersion()
   274  	}
   275  	if err != nil {
   276  		return false, err
   277  	}
   278  	version := version.NewVersion(artifactoryVersion)
   279  	includeInfoFiles := version.AtLeast(_go.ArtifactoryMinSupportedVersionForInfoFile)
   280  	return includeInfoFiles, nil
   281  }