github.com/jfrog/jfrog-cli-core/v2@v2.52.0/artifactory/commands/buildinfo/buildappend.go (about)

     1  package buildinfo
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"strconv"
     9  	"time"
    10  
    11  	buildinfo "github.com/jfrog/build-info-go/entities"
    12  	"github.com/jfrog/jfrog-client-go/artifactory"
    13  	servicesutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils"
    14  
    15  	"github.com/jfrog/jfrog-cli-core/v2/artifactory/utils"
    16  	"github.com/jfrog/jfrog-cli-core/v2/common/build"
    17  	"github.com/jfrog/jfrog-cli-core/v2/utils/config"
    18  	"github.com/jfrog/jfrog-client-go/artifactory/services"
    19  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
    20  	"github.com/jfrog/jfrog-client-go/utils/log"
    21  )
    22  
    23  type BuildAppendCommand struct {
    24  	buildConfiguration  *build.BuildConfiguration
    25  	serverDetails       *config.ServerDetails
    26  	buildNameToAppend   string
    27  	buildNumberToAppend string
    28  }
    29  
    30  func NewBuildAppendCommand() *BuildAppendCommand {
    31  	return &BuildAppendCommand{}
    32  }
    33  
    34  func (bac *BuildAppendCommand) CommandName() string {
    35  	return "rt_build_append"
    36  }
    37  
    38  func (bac *BuildAppendCommand) ServerDetails() (*config.ServerDetails, error) {
    39  	return config.GetDefaultServerConf()
    40  }
    41  
    42  func (bac *BuildAppendCommand) Run() error {
    43  	log.Info("Running Build Append command...")
    44  	buildName, err := bac.buildConfiguration.GetBuildName()
    45  	if err != nil {
    46  		return err
    47  	}
    48  	buildNumber, err := bac.buildConfiguration.GetBuildNumber()
    49  	if err != nil {
    50  		return err
    51  	}
    52  	if err = build.SaveBuildGeneralDetails(buildName, buildNumber, bac.buildConfiguration.GetProject()); err != nil {
    53  		return err
    54  	}
    55  
    56  	// Create services manager to get build-info from Artifactory.
    57  	servicesManager, err := utils.CreateServiceManager(bac.serverDetails, -1, 0, false)
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	// Calculate build timestamp
    63  	timestamp, err := bac.getBuildTimestamp(servicesManager)
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	// Get checksum values from the build info artifact
    69  	checksumDetails, err := bac.getChecksumDetails(servicesManager, timestamp)
    70  	if err != nil {
    71  		return err
    72  	}
    73  
    74  	log.Debug("Appending build", bac.buildNameToAppend+"/"+bac.buildNumberToAppend, "to build info")
    75  	populateFunc := func(partial *buildinfo.Partial) {
    76  		partial.ModuleType = buildinfo.Build
    77  		partial.ModuleId = bac.buildNameToAppend + "/" + bac.buildNumberToAppend
    78  		partial.Checksum = buildinfo.Checksum{
    79  			Sha1: checksumDetails.Sha1,
    80  			Md5:  checksumDetails.Md5,
    81  		}
    82  	}
    83  	err = build.SavePartialBuildInfo(buildName, buildNumber, bac.buildConfiguration.GetProject(), populateFunc)
    84  	if err == nil {
    85  		log.Info("Build", bac.buildNameToAppend+"/"+bac.buildNumberToAppend, "successfully appended to", buildName+"/"+buildNumber)
    86  	}
    87  	return err
    88  }
    89  
    90  func (bac *BuildAppendCommand) SetServerDetails(serverDetails *config.ServerDetails) *BuildAppendCommand {
    91  	bac.serverDetails = serverDetails
    92  	return bac
    93  }
    94  
    95  func (bac *BuildAppendCommand) SetBuildConfiguration(buildConfiguration *build.BuildConfiguration) *BuildAppendCommand {
    96  	bac.buildConfiguration = buildConfiguration
    97  	return bac
    98  }
    99  
   100  func (bac *BuildAppendCommand) SetBuildNameToAppend(buildName string) *BuildAppendCommand {
   101  	bac.buildNameToAppend = buildName
   102  	return bac
   103  }
   104  
   105  func (bac *BuildAppendCommand) SetBuildNumberToAppend(buildNumber string) *BuildAppendCommand {
   106  	bac.buildNumberToAppend = buildNumber
   107  	return bac
   108  }
   109  
   110  // Get build timestamp of the build to append. The build timestamp has to be converted to milliseconds from epoch.
   111  // For example, start time of: 2020-11-27T14:33:38.538+0200 should be converted to 1606480418538.
   112  func (bac *BuildAppendCommand) getBuildTimestamp(servicesManager artifactory.ArtifactoryServicesManager) (int64, error) {
   113  	// Get published build-info from Artifactory.
   114  	buildInfoParams := services.BuildInfoParams{BuildName: bac.buildNameToAppend, BuildNumber: bac.buildNumberToAppend, ProjectKey: bac.buildConfiguration.GetProject()}
   115  	buildInfo, found, err := servicesManager.GetBuildInfo(buildInfoParams)
   116  	if err != nil {
   117  		return 0, err
   118  	}
   119  	buildString := fmt.Sprintf("Build %s/%s", bac.buildNameToAppend, bac.buildNumberToAppend)
   120  	if bac.buildConfiguration.GetProject() != "" {
   121  		buildString = buildString + " of project: " + bac.buildConfiguration.GetProject()
   122  	}
   123  	if !found {
   124  		return 0, errorutils.CheckErrorf(buildString + " not found in Artifactory.")
   125  	}
   126  
   127  	buildTime, err := time.Parse(buildinfo.TimeFormat, buildInfo.BuildInfo.Started)
   128  	if errorutils.CheckError(err) != nil {
   129  		return 0, err
   130  	}
   131  
   132  	// Convert from nanoseconds to milliseconds
   133  	timestamp := buildTime.UnixNano() / 1000000
   134  	log.Debug(buildString + ". Started: " + buildInfo.BuildInfo.Started + ". Calculated timestamp: " + strconv.FormatInt(timestamp, 10))
   135  
   136  	return timestamp, err
   137  }
   138  
   139  // Download MD5 and SHA1 from the build info artifact.
   140  func (bac *BuildAppendCommand) getChecksumDetails(servicesManager artifactory.ArtifactoryServicesManager, timestamp int64) (buildinfo.Checksum, error) {
   141  	// Run AQL query for build
   142  	stringTimestamp := strconv.FormatInt(timestamp, 10)
   143  	aqlQuery := servicesutils.CreateAqlQueryForBuildInfoJson(bac.buildConfiguration.GetProject(), bac.buildNameToAppend, bac.buildNumberToAppend, stringTimestamp)
   144  	stream, err := servicesManager.Aql(aqlQuery)
   145  	if err != nil {
   146  		return buildinfo.Checksum{}, err
   147  	}
   148  	defer func() {
   149  		err = errors.Join(err, errorutils.CheckError(stream.Close()))
   150  	}()
   151  
   152  	// Parse AQL results
   153  	aqlResults, err := io.ReadAll(stream)
   154  	if err != nil {
   155  		return buildinfo.Checksum{}, errorutils.CheckError(err)
   156  	}
   157  	parsedResult := new(servicesutils.AqlSearchResult)
   158  	if err = json.Unmarshal(aqlResults, parsedResult); err != nil {
   159  		return buildinfo.Checksum{}, errorutils.CheckError(err)
   160  	}
   161  	if len(parsedResult.Results) == 0 {
   162  		return buildinfo.Checksum{}, errorutils.CheckErrorf("Build '%s/%s' could not be found", bac.buildNameToAppend, bac.buildNumberToAppend)
   163  	}
   164  
   165  	// Verify checksum exist
   166  	sha1 := parsedResult.Results[0].Actual_Sha1
   167  	md5 := parsedResult.Results[0].Actual_Md5
   168  	if sha1 == "" || md5 == "" {
   169  		return buildinfo.Checksum{}, errorutils.CheckErrorf("Missing checksums for build-info: '%s/%s', sha1: '%s', md5: '%s'", bac.buildNameToAppend, bac.buildNumberToAppend, sha1, md5)
   170  	}
   171  
   172  	// Return checksums
   173  	return buildinfo.Checksum{Sha1: sha1, Md5: md5}, nil
   174  }