github.com/jfrog/jfrog-cli-core@v1.12.1/artifactory/commands/buildinfo/publish.go (about)

     1  package buildinfo
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/jfrog/jfrog-client-go/artifactory"
     6  	clientutils "github.com/jfrog/jfrog-client-go/utils"
     7  	"github.com/jfrog/jfrog-client-go/utils/log"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/jfrog/jfrog-cli-core/artifactory/utils"
    14  	"github.com/jfrog/jfrog-cli-core/utils/config"
    15  	"github.com/jfrog/jfrog-cli-core/utils/coreutils"
    16  	"github.com/jfrog/jfrog-client-go/artifactory/buildinfo"
    17  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
    18  )
    19  
    20  type BuildPublishCommand struct {
    21  	buildConfiguration *utils.BuildConfiguration
    22  	serverDetails      *config.ServerDetails
    23  	config             *buildinfo.Configuration
    24  	detailedSummary    bool
    25  	summary            *clientutils.Sha256Summary
    26  }
    27  
    28  func NewBuildPublishCommand() *BuildPublishCommand {
    29  	return &BuildPublishCommand{}
    30  }
    31  
    32  func (bpc *BuildPublishCommand) SetConfig(config *buildinfo.Configuration) *BuildPublishCommand {
    33  	bpc.config = config
    34  	return bpc
    35  }
    36  
    37  func (bpc *BuildPublishCommand) SetServerDetails(serverDetails *config.ServerDetails) *BuildPublishCommand {
    38  	bpc.serverDetails = serverDetails
    39  	return bpc
    40  }
    41  
    42  func (bpc *BuildPublishCommand) SetBuildConfiguration(buildConfiguration *utils.BuildConfiguration) *BuildPublishCommand {
    43  	bpc.buildConfiguration = buildConfiguration
    44  	return bpc
    45  }
    46  
    47  func (bpc *BuildPublishCommand) SetSummary(summary *clientutils.Sha256Summary) *BuildPublishCommand {
    48  	bpc.summary = summary
    49  	return bpc
    50  }
    51  
    52  func (bpc *BuildPublishCommand) GetSummary() *clientutils.Sha256Summary {
    53  	return bpc.summary
    54  }
    55  
    56  func (bpc *BuildPublishCommand) SetDetailedSummary(detailedSummary bool) *BuildPublishCommand {
    57  	bpc.detailedSummary = detailedSummary
    58  	return bpc
    59  }
    60  
    61  func (bpc *BuildPublishCommand) IsDetailedSummary() bool {
    62  	return bpc.detailedSummary
    63  }
    64  
    65  func (bpc *BuildPublishCommand) CommandName() string {
    66  	return "rt_build_publish"
    67  }
    68  
    69  func (bpc *BuildPublishCommand) ServerDetails() (*config.ServerDetails, error) {
    70  	return bpc.serverDetails, nil
    71  }
    72  
    73  func (bpc *BuildPublishCommand) Run() error {
    74  	servicesManager, err := utils.CreateServiceManager(bpc.serverDetails, -1, bpc.config.DryRun)
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	buildInfo, err := bpc.createBuildInfoFromPartials()
    80  	if err != nil {
    81  		return err
    82  	}
    83  
    84  	generatedBuildsInfo, err := utils.GetGeneratedBuildsInfo(bpc.buildConfiguration.BuildName, bpc.buildConfiguration.BuildNumber, bpc.buildConfiguration.Project)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	for _, v := range generatedBuildsInfo {
    90  		buildInfo.Append(v)
    91  	}
    92  	summary, err := servicesManager.PublishBuildInfo(buildInfo, bpc.buildConfiguration.Project)
    93  	if bpc.IsDetailedSummary() {
    94  		bpc.SetSummary(summary)
    95  	}
    96  	if err != nil || bpc.config.DryRun {
    97  		return err
    98  	}
    99  
   100  	buildLink, err := bpc.constructBuildInfoUiUrl(servicesManager, buildInfo.Started)
   101  	if err != nil {
   102  		return err
   103  	}
   104  	log.Info("Build info successfully deployed. Browse it in Artifactory under " + buildLink)
   105  	return utils.RemoveBuildDir(bpc.buildConfiguration.BuildName, bpc.buildConfiguration.BuildNumber, bpc.buildConfiguration.Project)
   106  }
   107  
   108  func (bpc *BuildPublishCommand) constructBuildInfoUiUrl(servicesManager artifactory.ArtifactoryServicesManager, buildInfoStarted string) (string, error) {
   109  	buildTime, err := time.Parse(buildinfo.TimeFormat, buildInfoStarted)
   110  	if errorutils.CheckError(err) != nil {
   111  		return "", err
   112  	}
   113  	artVersion, err := servicesManager.GetVersion()
   114  	if err != nil {
   115  		return "", err
   116  	}
   117  	artVersionSlice := strings.Split(artVersion, ".")
   118  	majorVersion, err := strconv.Atoi(artVersionSlice[0])
   119  	if errorutils.CheckError(err) != nil {
   120  		return "", err
   121  	}
   122  	return bpc.getBuildInfoUiUrl(majorVersion, buildTime), nil
   123  }
   124  
   125  func (bpc *BuildPublishCommand) getBuildInfoUiUrl(majorVersion int, buildTime time.Time) string {
   126  	if majorVersion <= 6 {
   127  		return fmt.Sprintf("%vartifactory/webapp/#/builds/%v/%v",
   128  			bpc.serverDetails.GetUrl(), bpc.buildConfiguration.BuildName, bpc.buildConfiguration.BuildNumber)
   129  	} else if bpc.buildConfiguration.Project != "" {
   130  		timestamp := buildTime.UnixNano() / 1000000
   131  		return fmt.Sprintf("%vui/builds/%v/%v/%v/published?buildRepo=%v-build-info&projectKey=%v",
   132  			bpc.serverDetails.GetUrl(), bpc.buildConfiguration.BuildName, bpc.buildConfiguration.BuildNumber, strconv.FormatInt(timestamp, 10), bpc.buildConfiguration.Project, bpc.buildConfiguration.Project)
   133  	}
   134  	timestamp := buildTime.UnixNano() / 1000000
   135  	return fmt.Sprintf("%vui/builds/%v/%v/%v/published?buildRepo=artifactory-build-info",
   136  		bpc.serverDetails.GetUrl(), bpc.buildConfiguration.BuildName, bpc.buildConfiguration.BuildNumber, strconv.FormatInt(timestamp, 10))
   137  }
   138  
   139  func (bpc *BuildPublishCommand) createBuildInfoFromPartials() (*buildinfo.BuildInfo, error) {
   140  	buildName := bpc.buildConfiguration.BuildName
   141  	buildNumber := bpc.buildConfiguration.BuildNumber
   142  	projectKey := bpc.buildConfiguration.Project
   143  	partials, err := utils.ReadPartialBuildInfoFiles(buildName, buildNumber, projectKey)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  	sort.Sort(partials)
   148  
   149  	buildInfo := buildinfo.New()
   150  	buildInfo.SetAgentName(coreutils.GetCliUserAgentName())
   151  	buildInfo.SetAgentVersion(coreutils.GetCliUserAgentVersion())
   152  	buildInfo.SetBuildAgentVersion(coreutils.GetClientAgentVersion())
   153  	buildInfo.Name = buildName
   154  	buildInfo.Number = buildNumber
   155  	buildGeneralDetails, err := utils.ReadBuildInfoGeneralDetails(buildName, buildNumber, projectKey)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  	buildInfo.Started = buildGeneralDetails.Timestamp.Format(buildinfo.TimeFormat)
   160  	modules, env, vcsList, issues, err := extractBuildInfoData(partials, bpc.config.IncludeFilter(), bpc.config.ExcludeFilter())
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  	if len(env) != 0 {
   165  		buildInfo.Properties = env
   166  	}
   167  	buildInfo.ArtifactoryPrincipal = bpc.serverDetails.User
   168  	buildInfo.BuildUrl = bpc.config.BuildUrl
   169  	for _, vcs := range vcsList {
   170  		buildInfo.VcsList = append(buildInfo.VcsList, vcs)
   171  	}
   172  
   173  	// Check for Tracker as it must be set
   174  	if issues.Tracker != nil && issues.Tracker.Name != "" {
   175  		buildInfo.Issues = &issues
   176  	}
   177  	for _, module := range modules {
   178  		if module.Id == "" {
   179  			module.Id = buildName
   180  		}
   181  		buildInfo.Modules = append(buildInfo.Modules, module)
   182  	}
   183  	return buildInfo, nil
   184  }
   185  
   186  func extractBuildInfoData(partials buildinfo.Partials, includeFilter, excludeFilter buildinfo.Filter) ([]buildinfo.Module, buildinfo.Env, []buildinfo.Vcs, buildinfo.Issues, error) {
   187  	var vcs []buildinfo.Vcs
   188  	var issues buildinfo.Issues
   189  	env := make(map[string]string)
   190  	partialModules := make(map[string]*partialModule)
   191  	issuesMap := make(map[string]*buildinfo.AffectedIssue)
   192  	for _, partial := range partials {
   193  		moduleId := partial.ModuleId
   194  		if partialModules[moduleId] == nil {
   195  			partialModules[moduleId] = &partialModule{moduleType: partial.ModuleType}
   196  		}
   197  		switch {
   198  		case partial.Artifacts != nil:
   199  			for _, artifact := range partial.Artifacts {
   200  				addArtifactToPartialModule(artifact, moduleId, partialModules)
   201  			}
   202  		case partial.Dependencies != nil:
   203  			for _, dependency := range partial.Dependencies {
   204  				addDependencyToPartialModule(dependency, moduleId, partialModules)
   205  			}
   206  		case partial.VcsList != nil:
   207  			for _, partialVcs := range partial.VcsList {
   208  				vcs = append(vcs, partialVcs)
   209  			}
   210  			if partial.Issues == nil {
   211  				continue
   212  			}
   213  			// Collect issues.
   214  			issues.Tracker = partial.Issues.Tracker
   215  			issues.AggregateBuildIssues = partial.Issues.AggregateBuildIssues
   216  			issues.AggregationBuildStatus = partial.Issues.AggregationBuildStatus
   217  			// If affected issues exist, add them to issues map
   218  			if partial.Issues.AffectedIssues != nil {
   219  				for i, issue := range partial.Issues.AffectedIssues {
   220  					issuesMap[issue.Key] = &partial.Issues.AffectedIssues[i]
   221  				}
   222  			}
   223  		case partial.Env != nil:
   224  			envAfterIncludeFilter, e := includeFilter(partial.Env)
   225  			if errorutils.CheckError(e) != nil {
   226  				return partialModulesToModules(partialModules), env, vcs, issues, e
   227  			}
   228  			envAfterExcludeFilter, e := excludeFilter(envAfterIncludeFilter)
   229  			if errorutils.CheckError(e) != nil {
   230  				return partialModulesToModules(partialModules), env, vcs, issues, e
   231  			}
   232  			for k, v := range envAfterExcludeFilter {
   233  				env[k] = v
   234  			}
   235  		case partial.ModuleType == buildinfo.Build:
   236  			partialModules[moduleId].checksum = partial.Checksum
   237  		}
   238  	}
   239  	return partialModulesToModules(partialModules), env, vcs, issuesMapToArray(issues, issuesMap), nil
   240  }
   241  
   242  func partialModulesToModules(partialModules map[string]*partialModule) []buildinfo.Module {
   243  	var modules []buildinfo.Module
   244  	for moduleId, singlePartialModule := range partialModules {
   245  		moduleArtifacts := artifactsMapToList(singlePartialModule.artifacts)
   246  		moduleDependencies := dependenciesMapToList(singlePartialModule.dependencies)
   247  		modules = append(modules, *createModule(moduleId, singlePartialModule.moduleType, singlePartialModule.checksum, moduleArtifacts, moduleDependencies))
   248  	}
   249  	return modules
   250  }
   251  
   252  func issuesMapToArray(issues buildinfo.Issues, issuesMap map[string]*buildinfo.AffectedIssue) buildinfo.Issues {
   253  	for _, issue := range issuesMap {
   254  		issues.AffectedIssues = append(issues.AffectedIssues, *issue)
   255  	}
   256  	return issues
   257  }
   258  
   259  func addDependencyToPartialModule(dependency buildinfo.Dependency, moduleId string, partialModules map[string]*partialModule) {
   260  	// init map if needed
   261  	if partialModules[moduleId].dependencies == nil {
   262  		partialModules[moduleId].dependencies = make(map[string]buildinfo.Dependency)
   263  	}
   264  	key := fmt.Sprintf("%s-%s-%s-%s", dependency.Id, dependency.Sha1, dependency.Md5, dependency.Scopes)
   265  	partialModules[moduleId].dependencies[key] = dependency
   266  }
   267  
   268  func addArtifactToPartialModule(artifact buildinfo.Artifact, moduleId string, partialModules map[string]*partialModule) {
   269  	// init map if needed
   270  	if partialModules[moduleId].artifacts == nil {
   271  		partialModules[moduleId].artifacts = make(map[string]buildinfo.Artifact)
   272  	}
   273  	key := fmt.Sprintf("%s-%s-%s", artifact.Name, artifact.Sha1, artifact.Md5)
   274  	partialModules[moduleId].artifacts[key] = artifact
   275  }
   276  
   277  func artifactsMapToList(artifactsMap map[string]buildinfo.Artifact) []buildinfo.Artifact {
   278  	var artifacts []buildinfo.Artifact
   279  	for _, artifact := range artifactsMap {
   280  		artifacts = append(artifacts, artifact)
   281  	}
   282  	return artifacts
   283  }
   284  
   285  func dependenciesMapToList(dependenciesMap map[string]buildinfo.Dependency) []buildinfo.Dependency {
   286  	var dependencies []buildinfo.Dependency
   287  	for _, dependency := range dependenciesMap {
   288  		dependencies = append(dependencies, dependency)
   289  	}
   290  	return dependencies
   291  }
   292  
   293  func createModule(moduleId string, moduleType buildinfo.ModuleType, checksum buildinfo.Checksum, artifacts []buildinfo.Artifact, dependencies []buildinfo.Dependency) *buildinfo.Module {
   294  	module := createDefaultModule(moduleId)
   295  	module.Type = moduleType
   296  	module.Checksum = checksum
   297  	if artifacts != nil && len(artifacts) > 0 {
   298  		module.Artifacts = append(module.Artifacts, artifacts...)
   299  	}
   300  	if dependencies != nil && len(dependencies) > 0 {
   301  		module.Dependencies = append(module.Dependencies, dependencies...)
   302  	}
   303  	return module
   304  }
   305  
   306  func createDefaultModule(moduleId string) *buildinfo.Module {
   307  	return &buildinfo.Module{
   308  		Id:           moduleId,
   309  		Properties:   map[string][]string{},
   310  		Artifacts:    []buildinfo.Artifact{},
   311  		Dependencies: []buildinfo.Dependency{},
   312  	}
   313  }
   314  
   315  type partialModule struct {
   316  	moduleType   buildinfo.ModuleType
   317  	artifacts    map[string]buildinfo.Artifact
   318  	dependencies map[string]buildinfo.Dependency
   319  	checksum     buildinfo.Checksum
   320  }