github.com/jfrog/jfrog-cli-core/v2@v2.51.0/artifactory/commands/golang/go.go (about) 1 package golang 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "github.com/jfrog/build-info-go/build" 8 biutils "github.com/jfrog/build-info-go/utils" 9 buildUtils "github.com/jfrog/jfrog-cli-core/v2/common/build" 10 "github.com/jfrog/jfrog-cli-core/v2/common/project" 11 "github.com/jfrog/jfrog-cli-core/v2/utils/config" 12 "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" 13 goutils "github.com/jfrog/jfrog-cli-core/v2/utils/golang" 14 "github.com/jfrog/jfrog-client-go/auth" 15 "github.com/jfrog/jfrog-client-go/http/httpclient" 16 rtutils "github.com/jfrog/jfrog-client-go/utils" 17 "github.com/jfrog/jfrog-client-go/utils/errorutils" 18 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 19 "github.com/jfrog/jfrog-client-go/utils/log" 20 "net/http" 21 "os" 22 "path" 23 "path/filepath" 24 "strings" 25 ) 26 27 type GoCommand struct { 28 goArg []string 29 buildConfiguration *buildUtils.BuildConfiguration 30 deployerParams *project.RepositoryConfig 31 resolverParams *project.RepositoryConfig 32 configFilePath string 33 noFallback bool 34 } 35 36 func NewGoCommand() *GoCommand { 37 return &GoCommand{} 38 } 39 40 func (gc *GoCommand) SetConfigFilePath(configFilePath string) *GoCommand { 41 gc.configFilePath = configFilePath 42 return gc 43 } 44 45 func (gc *GoCommand) SetResolverParams(resolverParams *project.RepositoryConfig) *GoCommand { 46 gc.resolverParams = resolverParams 47 return gc 48 } 49 50 func (gc *GoCommand) SetDeployerParams(deployerParams *project.RepositoryConfig) *GoCommand { 51 gc.deployerParams = deployerParams 52 return gc 53 } 54 55 func (gc *GoCommand) SetBuildConfiguration(buildConfiguration *buildUtils.BuildConfiguration) *GoCommand { 56 gc.buildConfiguration = buildConfiguration 57 return gc 58 } 59 60 func (gc *GoCommand) SetGoArg(goArg []string) *GoCommand { 61 gc.goArg = goArg 62 return gc 63 } 64 65 func (gc *GoCommand) CommandName() string { 66 return "rt_go" 67 } 68 69 func (gc *GoCommand) ServerDetails() (*config.ServerDetails, error) { 70 // If deployer Artifactory details exists, returns it. 71 if gc.deployerParams != nil && !gc.deployerParams.IsServerDetailsEmpty() { 72 return gc.deployerParams.ServerDetails() 73 } 74 75 // If resolver Artifactory details exists, returns it. 76 if gc.resolverParams != nil && !gc.resolverParams.IsServerDetailsEmpty() { 77 return gc.resolverParams.ServerDetails() 78 } 79 80 vConfig, err := project.ReadConfigFile(gc.configFilePath, project.YAML) 81 if err != nil { 82 return nil, err 83 } 84 return buildUtils.GetServerDetails(vConfig) 85 } 86 87 func (gc *GoCommand) Run() error { 88 // Read config file. 89 log.Debug("Preparing to read the config file", gc.configFilePath) 90 vConfig, err := project.ReadConfigFile(gc.configFilePath, project.YAML) 91 if err != nil { 92 return err 93 } 94 95 // Extract resolution params. 96 gc.resolverParams, err = project.GetRepoConfigByPrefix(gc.configFilePath, project.ProjectConfigResolverPrefix, vConfig) 97 if err != nil { 98 return err 99 } 100 101 if vConfig.IsSet(project.ProjectConfigDeployerPrefix) { 102 // Extract deployer params. 103 gc.deployerParams, err = project.GetRepoConfigByPrefix(gc.configFilePath, project.ProjectConfigDeployerPrefix, vConfig) 104 if err != nil { 105 return err 106 } 107 } 108 109 // Extract build info information from the args. 110 gc.goArg, gc.buildConfiguration, err = buildUtils.ExtractBuildDetailsFromArgs(gc.goArg) 111 if err != nil { 112 return err 113 } 114 115 // Extract no-fallback flag from the args. 116 gc.goArg, err = gc.extractNoFallbackFromArgs() 117 if err != nil { 118 return err 119 } 120 return gc.run() 121 } 122 123 func (gc *GoCommand) extractNoFallbackFromArgs() (cleanArgs []string, err error) { 124 var flagIndex int 125 cleanArgs = append([]string(nil), gc.goArg...) 126 127 // Extract no-fallback boolean flag from the args. 128 flagIndex, gc.noFallback, err = coreutils.FindBooleanFlag("--no-fallback", cleanArgs) 129 if err != nil { 130 return 131 } 132 133 coreutils.RemoveFlagFromCommand(&cleanArgs, flagIndex, flagIndex) 134 return 135 } 136 137 func (gc *GoCommand) run() (err error) { 138 err = goutils.LogGoVersion() 139 if err != nil { 140 return 141 } 142 goBuildInfo, err := buildUtils.PrepareBuildPrerequisites(gc.buildConfiguration) 143 if err != nil { 144 return 145 } 146 defer func() { 147 if goBuildInfo != nil && err != nil { 148 err = errors.Join(err, goBuildInfo.Clean()) 149 } 150 }() 151 152 resolverDetails, err := gc.resolverParams.ServerDetails() 153 if err != nil { 154 return 155 } 156 repoUrl, err := goutils.GetArtifactoryRemoteRepoUrl(resolverDetails, gc.resolverParams.TargetRepo()) 157 if err != nil { 158 return 159 } 160 // If noFallback=false, missing packages will be fetched directly from VCS 161 if !gc.noFallback { 162 repoUrl += "|direct" 163 } 164 err = biutils.RunGo(gc.goArg, repoUrl) 165 if errorutils.CheckError(err) != nil { 166 err = coreutils.ConvertExitCodeError(err) 167 return 168 } 169 170 if goBuildInfo != nil { 171 // Need to collect build info 172 tempDirPath := "" 173 if isGoGetCommand := len(gc.goArg) > 0 && gc.goArg[0] == "get"; isGoGetCommand { 174 if len(gc.goArg) < 2 { 175 // Package name was not supplied. Invalid go get commend 176 err = errorutils.CheckErrorf("Invalid get command. Package name is missing") 177 return 178 } 179 tempDirPath, err = fileutils.CreateTempDir() 180 if err != nil { 181 return 182 } 183 // Cleanup the temp working directory at the end. 184 defer func() { 185 err = errors.Join(err, fileutils.RemoveTempDir(tempDirPath)) 186 }() 187 var serverDetails auth.ServiceDetails 188 serverDetails, err = resolverDetails.CreateArtAuthConfig() 189 if err != nil { 190 return 191 } 192 err = copyGoPackageFiles(tempDirPath, gc.goArg[1], gc.resolverParams.TargetRepo(), serverDetails) 193 if err != nil { 194 return 195 } 196 } 197 var goModule *build.GoModule 198 goModule, err = goBuildInfo.AddGoModule(tempDirPath) 199 if errorutils.CheckError(err) != nil { 200 return 201 } 202 if gc.buildConfiguration.GetModule() != "" { 203 goModule.SetName(gc.buildConfiguration.GetModule()) 204 } 205 err = errorutils.CheckError(goModule.CalcDependencies()) 206 } 207 208 return 209 } 210 211 // copyGoPackageFiles copies the package files from the go mod cache directory to the given destPath. 212 // The path to those cache files is retrieved using the supplied package name and Artifactory details. 213 func copyGoPackageFiles(destPath, packageName, rtTargetRepo string, authArtDetails auth.ServiceDetails) error { 214 packageFilesPath, err := getPackageFilePathFromArtifactory(packageName, rtTargetRepo, authArtDetails) 215 if err != nil { 216 return err 217 } 218 // Copy the entire content of the relevant Go pkg directory to the requested destination path. 219 err = biutils.CopyDir(packageFilesPath, destPath, true, nil) 220 if err != nil { 221 return fmt.Errorf("couldn't find suitable package files: %s", packageFilesPath) 222 } 223 // Set permission recursively 224 return coreutils.SetPermissionsRecursively(destPath, 0755) 225 } 226 227 // getPackageFilePathFromArtifactory returns a string that represents the package files cache path. 228 // In most cases the path to those cache files is retrieved using the supplied package name and Artifactory details. 229 // However, if the user asked for a specific version (package@vX.Y.Z) the unnecessary call to Artifactory is avoided. 230 func getPackageFilePathFromArtifactory(packageName, rtTargetRepo string, authArtDetails auth.ServiceDetails) (packageFilesPath string, err error) { 231 var version string 232 packageCachePath, err := biutils.GetGoModCachePath() 233 if errorutils.CheckError(err) != nil { 234 return 235 } 236 packageNameSplitted := strings.Split(packageName, "@") 237 name := packageNameSplitted[0] 238 // The case the user asks for a specific version 239 if len(packageNameSplitted) == 2 && strings.HasPrefix(packageNameSplitted[1], "v") { 240 version = packageNameSplitted[1] 241 } else { 242 branchName := "" 243 // The case the user asks for a specific branch 244 if len(packageNameSplitted) == 2 { 245 branchName = packageNameSplitted[1] 246 } 247 packageVersionRequest := buildPackageVersionRequest(name, branchName) 248 // Retrieve the package version using Artifactory 249 version, err = getPackageVersion(rtTargetRepo, packageVersionRequest, authArtDetails) 250 if err != nil { 251 return 252 } 253 } 254 packageFilesPath, err = getFileSystemPackagePath(packageCachePath, name, version) 255 return 256 257 } 258 259 // getPackageVersion returns the matching version for the packageName string using the Artifactory details that are provided. 260 // PackageName string should be in the following format: <Package Path>/@V/<Requested Branch Name>.info OR latest.info 261 // For example the jfrog/jfrog-cli/@v/master.info packageName will return the corresponding canonical version (vX.Y.Z) string for the jfrog-cli master branch. 262 func getPackageVersion(repoName, packageName string, details auth.ServiceDetails) (string, error) { 263 artifactoryApiUrl, err := rtutils.BuildUrl(details.GetUrl(), "api/go/"+repoName, make(map[string]string)) 264 if err != nil { 265 return "", err 266 } 267 artHttpDetails := details.CreateHttpClientDetails() 268 client, err := httpclient.ClientBuilder().Build() 269 if err != nil { 270 return "", err 271 } 272 artifactoryApiUrl = artifactoryApiUrl + "/" + packageName 273 resp, body, _, err := client.SendGet(artifactoryApiUrl, true, artHttpDetails, "") 274 if err != nil { 275 return "", err 276 } 277 if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK); err != nil { 278 return "", err 279 } 280 // Extract version from response 281 var version PackageVersionResponseContent 282 if err = json.Unmarshal(body, &version); err != nil { 283 return "", errorutils.CheckError(err) 284 } 285 return version.Version, nil 286 } 287 288 type PackageVersionResponseContent struct { 289 Version string `json:"Version,omitempty"` 290 } 291 292 // getFileSystemPackagePath returns a string that represents the package files cache path. 293 // In some cases when the path isn't represented by the package name, instead the name represents a specific project's directory's path. 294 // In this case we will scan the path until we find the package directory. 295 // Example : When running 'go get github.com/golang/mock/mockgen@v1.4.1' 296 // - "mockgen" is a directory inside "mock" package ("mockgen" doesn't contain "go.mod"). 297 // - go download and save the whole "mock" package in local cache under 'github.com/golang/mock@v1.4.1' -- > 298 // "go get" downloads and saves the whole "mock" package in the local cache under 'github.com/golang/mock@v1.4.1' 299 func getFileSystemPackagePath(packageCachePath, name, version string) (string, error) { 300 separator := string(filepath.Separator) 301 // For Windows OS 302 path := filepath.Clean(name) 303 for path != "" { 304 packagePath := filepath.Join(packageCachePath, path+"@"+version) 305 exists, err := fileutils.IsDirExists(packagePath, false) 306 if err != nil { 307 return "", err 308 } 309 if exists { 310 return packagePath, nil 311 } 312 // Remove path's last element and check again 313 path, _ = filepath.Split(path) 314 path = strings.TrimSuffix(path, separator) 315 } 316 return "", errors.New("Could not find package: " + name + " in: " + packageCachePath) 317 } 318 319 // buildPackageVersionRequest returns a string representing the version request to Artifactory. 320 // The resulted string is in the following format: "<Package Name>/@V/<Branch Name>.info". 321 // If a branch name is not given, the branch name will be replaced with the "latest" keyword. 322 // ("<Package Name>/@V/latest.info"). 323 func buildPackageVersionRequest(name, branchName string) string { 324 packageVersionRequest := path.Join(name, "@v") 325 if branchName != "" { 326 // A branch name was given by the user 327 return path.Join(packageVersionRequest, branchName+".info") 328 } 329 // No version was given to "go get" command, so the latest version should be requested 330 return path.Join(packageVersionRequest, "latest.info") 331 } 332 333 func SetArtifactoryAsResolutionServer(serverDetails *config.ServerDetails, depsRepo string) (err error) { 334 err = setGoProxy(serverDetails, depsRepo) 335 if err != nil { 336 err = fmt.Errorf("failed while setting Artifactory as a dependencies resolution registry: %s", err.Error()) 337 } 338 return 339 } 340 341 func setGoProxy(server *config.ServerDetails, remoteGoRepo string) error { 342 repoUrl, err := goutils.GetArtifactoryRemoteRepoUrl(server, remoteGoRepo) 343 if err != nil { 344 return err 345 } 346 repoUrl += "|direct" 347 return os.Setenv("GOPROXY", repoUrl) 348 }