github.com/jfrog/jfrog-cli-go@v1.22.1-0.20200318093948-4826ef344ffd/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-go/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 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 string) (Go, error) { 52 goProject := &goProject{version: version} 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() 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().GetCommonDetails().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 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 } 219 buildInfoModule := buildinfo.Module{Id: module, Artifacts: artifacts, Dependencies: buildInfoDependencies} 220 if module == "" { 221 buildInfoModule.Id = project.getId() 222 } 223 return &buildinfo.BuildInfo{Modules: []buildinfo.Module{buildInfoModule}} 224 } 225 226 // Get go project ID in the form of projectName:version 227 func (project *goProject) getId() string { 228 return project.moduleName 229 } 230 231 // Read go.mod file and add it as an artifact to the build info. 232 func (project *goProject) readModFile() error { 233 var err error 234 project.projectPath, err = cmd.GetProjectRoot() 235 if err != nil { 236 return errorutils.CheckError(err) 237 } 238 239 modFilePath := filepath.Join(project.projectPath, "go.mod") 240 modFile, err := os.Open(modFilePath) 241 if err != nil { 242 return errorutils.CheckError(err) 243 } 244 defer modFile.Close() 245 content, err := ioutil.ReadAll(modFile) 246 if err != nil { 247 return errorutils.CheckError(err) 248 } 249 250 // Read module name 251 project.moduleName, err = parseModuleName(string(content)) 252 if err != nil { 253 return err 254 } 255 256 checksums, err := checksum.Calc(bytes.NewBuffer(content)) 257 if err != nil { 258 return err 259 } 260 project.modContent = content 261 262 // Add mod file as artifact 263 artifact := buildinfo.Artifact{Name: project.version + ".mod", Type: "mod"} 264 artifact.Checksum = &buildinfo.Checksum{Sha1: checksums[checksum.SHA1], Md5: checksums[checksum.MD5]} 265 project.artifacts = append(project.artifacts, artifact) 266 return nil 267 } 268 269 // Archive the go project. 270 // Returns the path of the temp archived project file. 271 func (project *goProject) archiveProject(version, tempDir string) (string, error) { 272 tempFile, err := ioutil.TempFile(tempDir, "project.zip") 273 274 if err != nil { 275 return "", errorutils.CheckError(err) 276 } 277 defer tempFile.Close() 278 err = archiveProject(tempFile, project.projectPath, project.moduleName, version) 279 if err != nil { 280 return "", errorutils.CheckError(err) 281 } 282 // Double-check that the paths within the zip file are well-formed. 283 fi, err := tempFile.Stat() 284 if err != nil { 285 return "", err 286 } 287 z, err := zip.NewReader(tempFile, fi.Size()) 288 if err != nil { 289 return "", err 290 } 291 prefix := project.moduleName + "@" + version + "/" 292 for _, f := range z.File { 293 if !strings.HasPrefix(f.Name, prefix) { 294 return "", fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], f.Name) 295 } 296 } 297 // Sync the file before renaming it 298 if err := tempFile.Sync(); err != nil { 299 return "", err 300 } 301 if err := tempFile.Close(); err != nil { 302 return "", err 303 } 304 fileDetails, err := fileutils.GetFileDetails(tempFile.Name()) 305 if err != nil { 306 return "", err 307 } 308 309 artifact := buildinfo.Artifact{Name: version + ".zip", Type: "zip"} 310 artifact.Checksum = &buildinfo.Checksum{Sha1: fileDetails.Checksum.Sha1, Md5: fileDetails.Checksum.Md5} 311 project.artifacts = append(project.artifacts, artifact) 312 return tempFile.Name(), nil 313 } 314 315 // Add the info file also as an artifact to be part of the build info. 316 func (project *goProject) addInfoFileToBuildInfo(infoFilePath string) error { 317 fileDetails, err := fileutils.GetFileDetails(infoFilePath) 318 if err != nil { 319 return err 320 } 321 322 artifact := buildinfo.Artifact{Name: project.version + ".info", Type: "info"} 323 artifact.Checksum = &buildinfo.Checksum{Sha1: fileDetails.Checksum.Sha1, Md5: fileDetails.Checksum.Md5} 324 project.artifacts = append(project.artifacts, artifact) 325 return nil 326 } 327 328 // Parse module name from go.mod content. 329 func parseModuleName(modContent string) (string, error) { 330 r, err := regexp.Compile(`module "?([\w\.@:%_\+-.~#?&]+/?.+\w)`) 331 if err != nil { 332 return "", errorutils.CheckError(err) 333 } 334 lines := strings.Split(modContent, "\n") 335 for _, v := range lines { 336 matches := r.FindStringSubmatch(v) 337 if len(matches) == 2 { 338 return matches[1], nil 339 } 340 } 341 342 return "", errorutils.CheckError(errors.New("Module name missing in go.mod file")) 343 } 344 345 type goInfo struct { 346 Version string `json:"Version"` 347 Time string `json:"Time"` 348 }