github.com/jfrog/jfrog-cli-core/v2@v2.52.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  }