github.com/jfrog/jfrog-cli-core@v1.12.1/artifactory/utils/golang/project/project.go (about) 1 package project 2 3 import ( 4 "archive/zip" 5 "bytes" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "os" 11 "path/filepath" 12 "regexp" 13 "strings" 14 "time" 15 16 "github.com/jfrog/gocmd/cmd" 17 "github.com/jfrog/gocmd/executers" 18 executersutils "github.com/jfrog/gocmd/executers/utils" 19 "github.com/jfrog/jfrog-cli-core/artifactory/utils" 20 "github.com/jfrog/jfrog-client-go/artifactory" 21 "github.com/jfrog/jfrog-client-go/artifactory/buildinfo" 22 _go "github.com/jfrog/jfrog-client-go/artifactory/services/go" 23 servicesutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" 24 cliutils "github.com/jfrog/jfrog-client-go/utils" 25 "github.com/jfrog/jfrog-client-go/utils/errorutils" 26 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 27 "github.com/jfrog/jfrog-client-go/utils/io/fileutils/checksum" 28 "github.com/jfrog/jfrog-client-go/utils/log" 29 "github.com/jfrog/jfrog-client-go/utils/version" 30 ) 31 32 // Represent go project 33 type Go interface { 34 Dependencies() []executers.Package 35 CreateBuildInfoDependencies(includeInfoFiles bool) error 36 PublishPackage(targetRepo, buildName, buildNumber, projectKey string, servicesManager artifactory.ArtifactoryServicesManager) (*servicesutils.OperationSummary, error) 37 PublishDependencies(targetRepo string, servicesManager artifactory.ArtifactoryServicesManager, includeDepSlice []string) (succeeded, failed int, err error) 38 BuildInfo(includeArtifacts bool, module, targetRepository string) *buildinfo.BuildInfo 39 LoadDependencies() error 40 } 41 42 type goProject struct { 43 dependencies []executers.Package 44 artifacts []buildinfo.Artifact 45 modContent []byte 46 moduleName string 47 version string 48 projectPath string 49 } 50 51 // Load go project. 52 func Load(version, projectPath string) (Go, error) { 53 goProject := &goProject{version: version, projectPath: projectPath} 54 err := goProject.readModFile() 55 return goProject, err 56 } 57 58 // Get the go project dependencies. 59 func (project *goProject) Dependencies() []executers.Package { 60 return project.dependencies 61 } 62 63 // Get the go project dependencies. 64 func (project *goProject) CreateBuildInfoDependencies(includeInfoFiles bool) error { 65 for i, dep := range project.dependencies { 66 err := dep.CreateBuildInfoDependencies(includeInfoFiles) 67 if err != nil { 68 return err 69 } 70 project.dependencies[i] = dep 71 } 72 return nil 73 } 74 75 // Get the project dependencies. 76 func (project *goProject) LoadDependencies() error { 77 var err error 78 project.dependencies, err = project.loadDependencies() 79 return err 80 } 81 82 func (project *goProject) loadDependencies() ([]executers.Package, error) { 83 cachePath, err := executersutils.GetCachePath() 84 if err != nil { 85 return nil, err 86 } 87 modulesMap, err := cmd.GetDependenciesList(project.projectPath) 88 if err != nil { 89 return nil, err 90 } 91 if modulesMap == nil { 92 return nil, nil 93 } 94 return executers.GetDependencies(cachePath, modulesMap) 95 } 96 97 // Publish go project to Artifactory. 98 func (project *goProject) PublishPackage(targetRepo, buildName, buildNumber, projectKey string, servicesManager artifactory.ArtifactoryServicesManager) (*servicesutils.OperationSummary, error) { 99 log.Info("Publishing", project.getId(), "to", targetRepo) 100 101 props, err := utils.CreateBuildProperties(buildName, buildNumber, projectKey) 102 if err != nil { 103 return nil, err 104 } 105 106 // Temp directory for the project archive. 107 // The directory will be deleted at the end. 108 tempDirPath, err := fileutils.CreateTempDir() 109 if err != nil { 110 return nil, err 111 } 112 defer fileutils.RemoveTempDir(tempDirPath) 113 114 params := _go.NewGoParams() 115 params.Version = project.version 116 params.Props = props 117 params.TargetRepo = targetRepo 118 params.ModuleId = project.getId() 119 params.ModContent = project.modContent 120 params.ModPath = filepath.Join(project.projectPath, "go.mod") 121 params.ZipPath, err = project.archiveProject(project.version, tempDirPath) 122 if err != nil { 123 return nil, err 124 } 125 // Create the info file if Artifactory version is 6.10.0 and above. 126 artifactoryVersion, err := servicesManager.GetConfig().GetServiceDetails().GetVersion() 127 if err != nil { 128 return nil, err 129 } 130 version := version.NewVersion(artifactoryVersion) 131 if version.AtLeast(_go.ArtifactoryMinSupportedVersionForInfoFile) { 132 pathToInfo, err := project.createInfoFile() 133 if err != nil { 134 return nil, err 135 } 136 defer os.Remove(pathToInfo) 137 if len(buildName) > 0 && len(buildNumber) > 0 { 138 err = project.addInfoFileToBuildInfo(pathToInfo) 139 if err != nil { 140 return nil, err 141 } 142 } 143 params.InfoPath = pathToInfo 144 } 145 146 return servicesManager.PublishGoProject(params) 147 } 148 149 // Creates the info file. 150 // Returns the path to that file. 151 func (project *goProject) createInfoFile() (string, error) { 152 log.Debug("Creating info file", project.projectPath) 153 currentTime := time.Now().Format("2006-01-02T15:04:05Z") 154 goInfoContent := goInfo{Version: project.version, Time: currentTime} 155 content, err := json.Marshal(&goInfoContent) 156 if err != nil { 157 return "", errorutils.CheckError(err) 158 } 159 file, err := os.Create(project.version + ".info") 160 if err != nil { 161 return "", errorutils.CheckError(err) 162 } 163 defer file.Close() 164 _, err = file.Write(content) 165 if err != nil { 166 return "", errorutils.CheckError(err) 167 } 168 path, err := filepath.Abs(file.Name()) 169 if err != nil { 170 return "", errorutils.CheckError(err) 171 } 172 log.Debug("Info file was successfully created:", path) 173 return path, nil 174 } 175 176 func (project *goProject) PublishDependencies(targetRepo string, servicesManager artifactory.ArtifactoryServicesManager, includeDepSlice []string) (succeeded, failed int, err error) { 177 log.Info("Publishing package dependencies...") 178 includeDep := cliutils.ConvertSliceToMap(includeDepSlice) 179 180 skip := 0 181 _, includeAll := includeDep["ALL"] 182 dependencies := project.Dependencies() 183 for _, dependency := range dependencies { 184 includeDependency := includeAll 185 if !includeDependency { 186 if _, included := includeDep[dependency.GetId()]; included { 187 includeDependency = true 188 } 189 } 190 if includeDependency { 191 err = dependency.Publish("", targetRepo, servicesManager) 192 if err != nil { 193 err = errors.New("Failed to publish " + dependency.GetId() + " due to: " + err.Error()) 194 log.Error("Failed to publish", dependency.GetId(), ":", err) 195 } else { 196 succeeded++ 197 } 198 continue 199 } 200 skip++ 201 } 202 203 failed = len(dependencies) - succeeded - skip 204 if failed > 0 { 205 err = errors.New("Publishing project dependencies finished with errors. Please review the logs.") 206 } 207 return succeeded, failed, err 208 } 209 210 // Get the build info of the go project 211 func (project *goProject) BuildInfo(includeArtifacts bool, module, targetRepository string) *buildinfo.BuildInfo { 212 buildInfoDependencies := []buildinfo.Dependency{} 213 for _, dep := range project.dependencies { 214 buildInfoDependencies = append(buildInfoDependencies, dep.Dependencies()...) 215 } 216 var artifacts []buildinfo.Artifact 217 if includeArtifacts { 218 artifacts = project.artifacts 219 // Add artifacts target-path. 220 moduleId := strings.Split(project.moduleName, ":") 221 for i := range artifacts { 222 artifact := &artifacts[i] 223 targetPath := targetRepository 224 _ = _go.CreateUrlPath(moduleId[0], project.version, "", "."+artifact.Type, &targetPath) 225 artifact.Path = targetPath 226 } 227 } 228 buildInfoModule := buildinfo.Module{Id: module, Type: buildinfo.Go, Artifacts: artifacts, Dependencies: buildInfoDependencies} 229 if module == "" { 230 buildInfoModule.Id = project.getId() 231 } 232 return &buildinfo.BuildInfo{Modules: []buildinfo.Module{buildInfoModule}} 233 } 234 235 // Get go project ID in the form of projectName:version 236 func (project *goProject) getId() string { 237 return project.moduleName 238 } 239 240 // Read go.mod file and add it as an artifact to the build info. 241 func (project *goProject) readModFile() error { 242 var err error 243 if project.projectPath == "" { 244 project.projectPath, err = cmd.GetProjectRoot() 245 if err != nil { 246 return errorutils.CheckError(err) 247 } 248 } 249 250 modFilePath := filepath.Join(project.projectPath, "go.mod") 251 modFileExists, _ := fileutils.IsFileExists(modFilePath, true) 252 if !modFileExists { 253 return errorutils.CheckError(errors.New("Could not find project's go.mod in " + project.projectPath)) 254 } 255 modFile, err := os.Open(modFilePath) 256 if err != nil { 257 return err 258 } 259 defer modFile.Close() 260 content, err := io.ReadAll(modFile) 261 if err != nil { 262 return errorutils.CheckError(err) 263 } 264 265 // Read module name 266 project.moduleName, err = parseModuleName(string(content)) 267 if err != nil { 268 return err 269 } 270 271 checksums, err := checksum.Calc(bytes.NewBuffer(content)) 272 if err != nil { 273 return err 274 } 275 project.modContent = content 276 277 // Add mod file as artifact 278 artifact := buildinfo.Artifact{Name: project.version + ".mod", Type: "mod"} 279 artifact.Checksum = buildinfo.Checksum{Sha1: checksums[checksum.SHA1], Md5: checksums[checksum.MD5]} 280 project.artifacts = append(project.artifacts, artifact) 281 return nil 282 } 283 284 // Archive the go project. 285 // Returns the path of the temp archived project file. 286 func (project *goProject) archiveProject(version, tempDir string) (string, error) { 287 tempFile, err := os.CreateTemp(tempDir, "project.zip") 288 289 if err != nil { 290 return "", errorutils.CheckError(err) 291 } 292 defer tempFile.Close() 293 err = archiveProject(tempFile, project.projectPath, project.moduleName, version) 294 if err != nil { 295 return "", errorutils.CheckError(err) 296 } 297 // Double-check that the paths within the zip file are well-formed. 298 fi, err := tempFile.Stat() 299 if err != nil { 300 return "", err 301 } 302 z, err := zip.NewReader(tempFile, fi.Size()) 303 if err != nil { 304 return "", err 305 } 306 prefix := project.moduleName + "@" + version + "/" 307 for _, f := range z.File { 308 if !strings.HasPrefix(f.Name, prefix) { 309 return "", fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], f.Name) 310 } 311 } 312 // Sync the file before renaming it 313 if err := tempFile.Sync(); err != nil { 314 return "", err 315 } 316 if err := tempFile.Close(); err != nil { 317 return "", err 318 } 319 fileDetails, err := fileutils.GetFileDetails(tempFile.Name()) 320 if err != nil { 321 return "", err 322 } 323 324 artifact := buildinfo.Artifact{Name: version + ".zip", Type: "zip"} 325 artifact.Checksum = buildinfo.Checksum{Sha1: fileDetails.Checksum.Sha1, Md5: fileDetails.Checksum.Md5} 326 project.artifacts = append(project.artifacts, artifact) 327 return tempFile.Name(), nil 328 } 329 330 // Add the info file also as an artifact to be part of the build info. 331 func (project *goProject) addInfoFileToBuildInfo(infoFilePath string) error { 332 fileDetails, err := fileutils.GetFileDetails(infoFilePath) 333 if err != nil { 334 return err 335 } 336 337 artifact := buildinfo.Artifact{Name: project.version + ".info", Type: "info"} 338 artifact.Checksum = buildinfo.Checksum{Sha1: fileDetails.Checksum.Sha1, Md5: fileDetails.Checksum.Md5} 339 project.artifacts = append(project.artifacts, artifact) 340 return nil 341 } 342 343 // Parse module name from go.mod content. 344 func parseModuleName(modContent string) (string, error) { 345 r, err := regexp.Compile(`module "?([\w\.@:%_\+-.~#?&]+/?.+\w)`) 346 if err != nil { 347 return "", errorutils.CheckError(err) 348 } 349 lines := strings.Split(modContent, "\n") 350 for _, v := range lines { 351 matches := r.FindStringSubmatch(v) 352 if len(matches) == 2 { 353 return matches[1], nil 354 } 355 } 356 357 return "", errorutils.CheckError(errors.New("Module name missing in go.mod file")) 358 } 359 360 type goInfo struct { 361 Version string `json:"Version"` 362 Time string `json:"Time"` 363 }