github.com/jfrog/jfrog-cli-core/v2@v2.51.0/artifactory/commands/golang/publish.go (about) 1 package golang 2 3 import ( 4 "archive/zip" 5 "bytes" 6 "encoding/json" 7 "errors" 8 "fmt" 9 buildinfo "github.com/jfrog/build-info-go/entities" 10 biutils "github.com/jfrog/build-info-go/utils" 11 "github.com/jfrog/gofrog/version" 12 "io" 13 "os" 14 "path/filepath" 15 "strings" 16 "time" 17 18 "github.com/jfrog/jfrog-cli-core/v2/common/build" 19 goutils "github.com/jfrog/jfrog-cli-core/v2/utils/golang" 20 "github.com/jfrog/jfrog-client-go/artifactory" 21 _go "github.com/jfrog/jfrog-client-go/artifactory/services/go" 22 servicesutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" 23 "github.com/jfrog/jfrog-client-go/utils/errorutils" 24 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 25 "github.com/jfrog/jfrog-client-go/utils/log" 26 ) 27 28 // Publish go project to Artifactory. 29 func publishPackage(packageVersion, targetRepo, buildName, buildNumber, projectKey string, excludedPatterns []string, servicesManager artifactory.ArtifactoryServicesManager) (summary *servicesutils.OperationSummary, artifacts []buildinfo.Artifact, err error) { 30 projectPath, err := goutils.GetProjectRoot() 31 if err != nil { 32 return nil, nil, errorutils.CheckError(err) 33 } 34 35 collectBuildInfo := len(buildName) > 0 && len(buildNumber) > 0 36 modContent, modArtifact, err := readModFile(packageVersion, projectPath, collectBuildInfo) 37 if err != nil { 38 return nil, nil, err 39 } 40 41 // Read module name 42 moduleName, err := goutils.GetModuleName(projectPath) 43 if err != nil { 44 return nil, nil, err 45 } 46 47 log.Info("Publishing", moduleName, "to", targetRepo) 48 49 props, err := build.CreateBuildProperties(buildName, buildNumber, projectKey) 50 if err != nil { 51 return nil, nil, err 52 } 53 54 // Temp directory for the project archive. 55 // The directory will be deleted at the end. 56 tempDirPath, err := fileutils.CreateTempDir() 57 if err != nil { 58 return nil, nil, err 59 } 60 defer func() { 61 err = errors.Join(err, fileutils.RemoveTempDir(tempDirPath)) 62 }() 63 64 var zipArtifact *buildinfo.Artifact 65 params := _go.NewGoParams() 66 params.Version = packageVersion 67 params.Props = props 68 params.TargetRepo = targetRepo 69 params.ModuleId = moduleName 70 params.ModContent = modContent 71 params.ModPath = filepath.Join(projectPath, "go.mod") 72 params.ZipPath, zipArtifact, err = archive(moduleName, packageVersion, projectPath, tempDirPath, excludedPatterns) 73 if err != nil { 74 return nil, nil, err 75 } 76 if collectBuildInfo { 77 artifacts = []buildinfo.Artifact{*modArtifact, *zipArtifact} 78 } 79 80 // Create the info file if Artifactory version is 6.10.0 and above. 81 artifactoryVersion, err := servicesManager.GetConfig().GetServiceDetails().GetVersion() 82 if err != nil { 83 return nil, nil, err 84 } 85 version := version.NewVersion(artifactoryVersion) 86 if version.AtLeast(_go.ArtifactoryMinSupportedVersion) { 87 log.Debug("Creating info file", projectPath) 88 var pathToInfo string 89 pathToInfo, err = createInfoFile(packageVersion) 90 if err != nil { 91 return nil, nil, err 92 } 93 defer func() { 94 err = errors.Join(err, errorutils.CheckError(os.Remove(pathToInfo))) 95 }() 96 if collectBuildInfo { 97 var infoArtifact *buildinfo.Artifact 98 infoArtifact, err = createInfoFileArtifact(pathToInfo, packageVersion) 99 if err != nil { 100 return nil, nil, err 101 } 102 artifacts = append(artifacts, *infoArtifact) 103 } 104 params.InfoPath = pathToInfo 105 } 106 107 summary, err = servicesManager.PublishGoProject(params) 108 return summary, artifacts, err 109 } 110 111 // Creates the info file. 112 // Returns the path to that file. 113 func createInfoFile(packageVersion string) (path string, err error) { 114 currentTime := time.Now().Format("2006-01-02T15:04:05Z") 115 goInfoContent := goInfo{Version: packageVersion, Time: currentTime} 116 content, err := json.Marshal(&goInfoContent) 117 if err != nil { 118 return "", errorutils.CheckError(err) 119 } 120 file, err := os.Create(packageVersion + ".info") 121 if err != nil { 122 return "", errorutils.CheckError(err) 123 } 124 defer func() { 125 err = errors.Join(err, errorutils.CheckError(file.Close())) 126 }() 127 _, err = file.Write(content) 128 if err != nil { 129 return "", errorutils.CheckError(err) 130 } 131 path, err = filepath.Abs(file.Name()) 132 if err != nil { 133 return "", errorutils.CheckError(err) 134 } 135 log.Debug("Info file was successfully created:", path) 136 return path, nil 137 } 138 139 // Read go.mod file. 140 // Pass createArtifact = true to create an Artifact for build-info. 141 func readModFile(version, projectPath string, createArtifact bool) ([]byte, *buildinfo.Artifact, error) { 142 modFilePath := filepath.Join(projectPath, "go.mod") 143 modFileExists, _ := fileutils.IsFileExists(modFilePath, true) 144 if !modFileExists { 145 return nil, nil, errorutils.CheckErrorf("Could not find project's go.mod in " + projectPath) 146 } 147 modFile, err := os.Open(modFilePath) 148 if err != nil { 149 return nil, nil, err 150 } 151 defer func() { 152 err = errors.Join(err, errorutils.CheckError(modFile.Close())) 153 }() 154 content, err := io.ReadAll(modFile) 155 if err != nil { 156 return nil, nil, errorutils.CheckError(err) 157 } 158 159 if !createArtifact { 160 return content, nil, nil 161 } 162 163 checksums, err := biutils.CalcChecksums(bytes.NewBuffer(content)) 164 if err != nil { 165 return nil, nil, errorutils.CheckError(err) 166 } 167 168 // Add mod file as artifact 169 artifact := &buildinfo.Artifact{Name: version + ".mod", Type: "mod"} 170 artifact.Checksum = buildinfo.Checksum{Sha1: checksums[biutils.SHA1], Md5: checksums[biutils.MD5]} 171 return content, artifact, nil 172 } 173 174 // Archive the go project. 175 // Returns the path of the temp archived project file. 176 func archive(moduleName, version, projectPath, tempDir string, excludedPatterns []string) (name string, zipArtifact *buildinfo.Artifact, err error) { 177 openedFile := false 178 tempFile, err := os.CreateTemp(tempDir, "project.zip") 179 if err != nil { 180 return "", nil, errorutils.CheckError(err) 181 } 182 openedFile = true 183 defer func() { 184 if openedFile { 185 err = errors.Join(err, errorutils.CheckError(tempFile.Close())) 186 } 187 }() 188 if err = archiveProject(tempFile, projectPath, moduleName, version, excludedPatterns); err != nil { 189 return "", nil, errorutils.CheckError(err) 190 } 191 // Double-check that the paths within the zip file are well-formed. 192 fi, err := tempFile.Stat() 193 if err != nil { 194 return "", nil, err 195 } 196 z, err := zip.NewReader(tempFile, fi.Size()) 197 if err != nil { 198 return "", nil, err 199 } 200 prefix := moduleName + "@" + version + "/" 201 for _, f := range z.File { 202 if !strings.HasPrefix(f.Name, prefix) { 203 return "", nil, fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], f.Name) 204 } 205 } 206 // Sync the file before renaming it 207 if err = tempFile.Sync(); err != nil { 208 return "", nil, err 209 } 210 if err = tempFile.Close(); err != nil { 211 return "", nil, err 212 } 213 openedFile = false 214 fileDetails, err := fileutils.GetFileDetails(tempFile.Name(), true) 215 if err != nil { 216 return "", nil, err 217 } 218 219 zipArtifact = &buildinfo.Artifact{Name: version + ".zip", Type: "zip"} 220 zipArtifact.Checksum = buildinfo.Checksum{Sha1: fileDetails.Checksum.Sha1, Md5: fileDetails.Checksum.Md5} 221 return tempFile.Name(), zipArtifact, nil 222 } 223 224 // Add the info file also as an artifact to be part of the build info. 225 func createInfoFileArtifact(infoFilePath, packageVersion string) (*buildinfo.Artifact, error) { 226 fileDetails, err := fileutils.GetFileDetails(infoFilePath, true) 227 if err != nil { 228 return nil, err 229 } 230 231 artifact := &buildinfo.Artifact{Name: packageVersion + ".info", Type: "info"} 232 artifact.Checksum = buildinfo.Checksum{Sha1: fileDetails.Checksum.Sha1, Md5: fileDetails.Checksum.Md5} 233 return artifact, nil 234 } 235 236 type goInfo struct { 237 Version string `json:"Version"` 238 Time string `json:"Time"` 239 }