github.com/osievert/jfrog-cli-core@v1.2.7/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/ioutil" 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 cliutils "github.com/jfrog/jfrog-client-go/utils" 24 "github.com/jfrog/jfrog-client-go/utils/errorutils" 25 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 26 "github.com/jfrog/jfrog-client-go/utils/io/fileutils/checksum" 27 "github.com/jfrog/jfrog-client-go/utils/log" 28 "github.com/jfrog/jfrog-client-go/utils/version" 29 ) 30 31 // Represent go project 32 type Go interface { 33 Dependencies() []executers.Package 34 CreateBuildInfoDependencies(includeInfoFiles bool) error 35 PublishPackage(targetRepo, buildName, buildNumber string, servicesManager artifactory.ArtifactoryServicesManager) error 36 PublishDependencies(targetRepo string, servicesManager artifactory.ArtifactoryServicesManager, includeDepSlice []string) (succeeded, failed int, err error) 37 BuildInfo(includeArtifacts bool, module, targetRepository string) *buildinfo.BuildInfo 38 LoadDependencies() error 39 } 40 41 type goProject struct { 42 dependencies []executers.Package 43 artifacts []buildinfo.Artifact 44 modContent []byte 45 moduleName string 46 version string 47 projectPath string 48 } 49 50 // Load go project. 51 func Load(version, projectPath string) (Go, error) { 52 goProject := &goProject{version: version, projectPath: projectPath} 53 err := goProject.readModFile() 54 return goProject, err 55 } 56 57 // Get the go project dependencies. 58 func (project *goProject) Dependencies() []executers.Package { 59 return project.dependencies 60 } 61 62 // Get the go project dependencies. 63 func (project *goProject) CreateBuildInfoDependencies(includeInfoFiles bool) error { 64 for i, dep := range project.dependencies { 65 err := dep.CreateBuildInfoDependencies(includeInfoFiles) 66 if err != nil { 67 return err 68 } 69 project.dependencies[i] = dep 70 } 71 return nil 72 } 73 74 // Get the project dependencies. 75 func (project *goProject) LoadDependencies() error { 76 var err error 77 project.dependencies, err = project.loadDependencies() 78 return err 79 } 80 81 func (project *goProject) loadDependencies() ([]executers.Package, error) { 82 cachePath, err := executersutils.GetCachePath() 83 if err != nil { 84 return nil, err 85 } 86 modulesMap, err := cmd.GetDependenciesGraph(project.projectPath) 87 if err != nil { 88 return nil, err 89 } 90 if modulesMap == nil { 91 return nil, nil 92 } 93 return executers.GetDependencies(cachePath, modulesMap) 94 } 95 96 // Publish go project to Artifactory. 97 func (project *goProject) PublishPackage(targetRepo, buildName, buildNumber string, servicesManager artifactory.ArtifactoryServicesManager) error { 98 log.Info("Publishing", project.getId(), "to", targetRepo) 99 100 props, err := utils.CreateBuildProperties(buildName, buildNumber) 101 if err != nil { 102 return err 103 } 104 105 // Temp directory for the project archive. 106 // The directory will be deleted at the end. 107 tempDirPath, err := fileutils.CreateTempDir() 108 if err != nil { 109 return err 110 } 111 defer fileutils.RemoveTempDir(tempDirPath) 112 113 params := _go.NewGoParams() 114 params.Version = project.version 115 params.Props = props 116 params.TargetRepo = targetRepo 117 params.ModuleId = project.getId() 118 params.ModContent = project.modContent 119 params.ModPath = filepath.Join(project.projectPath, "go.mod") 120 params.ZipPath, err = project.archiveProject(project.version, tempDirPath) 121 if err != nil { 122 return err 123 } 124 // Create the info file if Artifactory version is 6.10.0 and above. 125 artifactoryVersion, err := servicesManager.GetConfig().GetServiceDetails().GetVersion() 126 if err != nil { 127 return err 128 } 129 version := version.NewVersion(artifactoryVersion) 130 if version.AtLeast(_go.ArtifactoryMinSupportedVersionForInfoFile) { 131 pathToInfo, err := project.createInfoFile() 132 if err != nil { 133 return err 134 } 135 defer os.Remove(pathToInfo) 136 if len(buildName) > 0 && len(buildNumber) > 0 { 137 err = project.addInfoFileToBuildInfo(pathToInfo) 138 if err != nil { 139 return err 140 } 141 } 142 params.InfoPath = pathToInfo 143 } 144 145 return servicesManager.PublishGoProject(params) 146 } 147 148 // Creates the info file. 149 // Returns the path to that file. 150 func (project *goProject) createInfoFile() (string, error) { 151 log.Debug("Creating info file", project.projectPath) 152 currentTime := time.Now().Format("2006-01-02T15:04:05Z") 153 goInfoContent := goInfo{Version: project.version, Time: currentTime} 154 content, err := json.Marshal(&goInfoContent) 155 if err != nil { 156 return "", errorutils.CheckError(err) 157 } 158 file, err := os.Create(project.version + ".info") 159 if err != nil { 160 return "", errorutils.CheckError(err) 161 } 162 defer file.Close() 163 _, err = file.Write(content) 164 if err != nil { 165 return "", errorutils.CheckError(err) 166 } 167 path, err := filepath.Abs(file.Name()) 168 if err != nil { 169 return "", errorutils.CheckError(err) 170 } 171 log.Debug("Info file was successfully created:", path) 172 return path, nil 173 } 174 175 func (project *goProject) PublishDependencies(targetRepo string, servicesManager artifactory.ArtifactoryServicesManager, includeDepSlice []string) (succeeded, failed int, err error) { 176 log.Info("Publishing package dependencies...") 177 includeDep := cliutils.ConvertSliceToMap(includeDepSlice) 178 179 skip := 0 180 _, includeAll := includeDep["ALL"] 181 dependencies := project.Dependencies() 182 for _, dependency := range dependencies { 183 includeDependency := includeAll 184 if !includeDependency { 185 if _, included := includeDep[dependency.GetId()]; included { 186 includeDependency = true 187 } 188 } 189 if includeDependency { 190 err = dependency.Publish("", targetRepo, servicesManager) 191 if err != nil { 192 err = errors.New("Failed to publish " + dependency.GetId() + " due to: " + err.Error()) 193 log.Error("Failed to publish", dependency.GetId(), ":", err) 194 } else { 195 succeeded++ 196 } 197 continue 198 } 199 skip++ 200 } 201 202 failed = len(dependencies) - succeeded - skip 203 if failed > 0 { 204 err = errors.New("Publishing project dependencies finished with errors. Please review the logs.") 205 } 206 return succeeded, failed, err 207 } 208 209 // Get the build info of the go project 210 func (project *goProject) BuildInfo(includeArtifacts bool, module, targetRepository string) *buildinfo.BuildInfo { 211 buildInfoDependencies := []buildinfo.Dependency{} 212 for _, dep := range project.dependencies { 213 buildInfoDependencies = append(buildInfoDependencies, dep.Dependencies()...) 214 } 215 var artifacts []buildinfo.Artifact 216 if includeArtifacts { 217 artifacts = project.artifacts 218 // Add artifacts target-path. 219 moduleId := strings.Split(project.moduleName, ":") 220 for i := range artifacts { 221 artifact := &artifacts[i] 222 targetPath := targetRepository 223 _ = _go.CreateUrlPath(moduleId[0], project.version, "", "."+artifact.Type, &targetPath) 224 artifact.Path = targetPath 225 } 226 } 227 buildInfoModule := buildinfo.Module{Id: module, Type: buildinfo.Go, Artifacts: artifacts, Dependencies: buildInfoDependencies} 228 if module == "" { 229 buildInfoModule.Id = project.getId() 230 } 231 return &buildinfo.BuildInfo{Modules: []buildinfo.Module{buildInfoModule}} 232 } 233 234 // Get go project ID in the form of projectName:version 235 func (project *goProject) getId() string { 236 return project.moduleName 237 } 238 239 // Read go.mod file and add it as an artifact to the build info. 240 func (project *goProject) readModFile() error { 241 var err error 242 if project.projectPath == "" { 243 project.projectPath, err = cmd.GetProjectRoot() 244 if err != nil { 245 return errorutils.CheckError(err) 246 } 247 } 248 249 modFilePath := filepath.Join(project.projectPath, "go.mod") 250 modFileExists, _ := fileutils.IsFileExists(modFilePath, true) 251 if !modFileExists { 252 log.Info("Could not find go.mod in ", project.projectPath) 253 return nil 254 } 255 modFile, err := os.Open(modFilePath) 256 if err != nil { 257 return err 258 } 259 defer modFile.Close() 260 content, err := ioutil.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 := ioutil.TempFile(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 }