diff --git a/artifactory/utils/vcs.go b/artifactory/utils/vcs.go index 02ba028..ddf9f0f 100644 --- a/artifactory/utils/vcs.go +++ b/artifactory/utils/vcs.go @@ -4,6 +4,7 @@ import ( "errors" buildinfo "github.com/jfrog/build-info-go/entities" gofrogcmd "github.com/jfrog/gofrog/io" + utils2 "github.com/jfrog/jfrog-cli-artifactory/evidence/utils" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/common/build" utilsconfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" @@ -16,6 +17,7 @@ import ( "io" "os" "os/exec" + "regexp" "strconv" "strings" ) @@ -24,6 +26,113 @@ const ( revisionRangeErrPrefix = "fatal: Invalid revision range" ) +type BuildAndVcsDetails interface { + ParseGitLogFromLastVcsRevision(gitDetails GitLogDetails, logRegExp *gofrogcmd.CmdOutputPattern, lastVcsRevision string) (err error) + GetPlainGitLogFromPreviousBuild(serverDetails *utilsconfig.ServerDetails, buildConfiguration *build.BuildConfiguration, gitDetails GitLogDetails) (string, error) + GetLastBuildLink(serverDetails *utilsconfig.ServerDetails, buildConfiguration *build.BuildConfiguration) (string, error) +} + +type GitLogDetails struct { + LogLimit int + PrettyFormat string + // Optional + DotGitPath string +} + +// ParseGitLogFromLastBuild Parses git commits from the last build's VCS revision. +// Calls git log with a custom format, and parses each line of the output with regexp. logRegExp is used to parse the log lines. +func ParseGitLogFromLastBuild(serverDetails *utilsconfig.ServerDetails, buildConfiguration *build.BuildConfiguration, gitDetails GitLogDetails, logRegExp *gofrogcmd.CmdOutputPattern) error { + vcsUrl, err := validateGitAndGetVcsUrl(&gitDetails) + if err != nil { + return err + } + + // Get latest build's VCS revision from Artifactory. + lastVcsRevision, err := getLatestVcsRevision(serverDetails, buildConfiguration, vcsUrl) + if err != nil { + return err + } + return ParseGitLogFromLastVcsRevision(gitDetails, logRegExp, lastVcsRevision) +} + +// GetPlainGitLogFromPreviousBuild Returns the git log output for the VCS revision for the previous build in position previousBuildPos. +// For previousBuildPos 0 the latest build is returned, for an input 1 the latest -1 is returned, etc. previousBuildPos must be 0 or above. +// Calls git log with a custom format, and returns the output as is. +// Return RevisionRangeError if revision isn't found (due to git history modification). +func GetPlainGitLogFromPreviousBuild(serverDetails *utilsconfig.ServerDetails, buildConfiguration *build.BuildConfiguration, gitDetails GitLogDetails) (string, error) { + vcsUrl, err := validateGitAndGetVcsUrl(&gitDetails) + if err != nil { + return "", err + } + + lastVcsRevision, err := getVcsFromPreviousBuild(serverDetails, buildConfiguration, vcsUrl) + if err != nil { + return "", err + } + + return getPlainGitLogFromLastVcsRevision(gitDetails, lastVcsRevision) +} + +func GetLastBuildLink(serverDetails *utilsconfig.ServerDetails, buildConfiguration *build.BuildConfiguration) (string, error) { + lastPublishedBuildInfo, err := getPreviousBuild(serverDetails, buildConfiguration, 0) + if err != nil { + return "", err + } + uri, err := convertToUiLink(lastPublishedBuildInfo) + if err != nil { + return "", err + } + return uri, nil +} + +// ParseGitLogFromLastVcsRevision Parses git log line by line, using the parser provided in logRegExp. +// Git log is parsed from lastVcsRevision to HEAD. +func ParseGitLogFromLastVcsRevision(gitDetails GitLogDetails, logRegExp *gofrogcmd.CmdOutputPattern, lastVcsRevision string) (err error) { + logCmd, cleanupFunc, err := prepareGitLogCommand(gitDetails, lastVcsRevision) + defer func() { + if cleanupFunc != nil { + err = errors.Join(err, cleanupFunc()) + } + }() + + errRegExp, err := createErrRegExpHandler(lastVcsRevision) + if err != nil { + return err + } + + // Run git command. + _, _, exitOk, err := gofrogcmd.RunCmdWithOutputParser(logCmd, false, logRegExp, errRegExp) + if errorutils.CheckError(err) != nil { + var revisionRangeError RevisionRangeError + if errors.As(err, &revisionRangeError) { + // Revision not found in range. Ignore and return. + log.Info(err.Error()) + return nil + } + return err + } + if !exitOk { + // May happen when trying to run git log for non-existing revision. + err = errorutils.CheckErrorf("failed executing git log command") + } + return err +} + +// GetDotGit Looks for the .git directory in the current directory and its parents. +func GetDotGit(providedDotGitPath string) (string, error) { + if providedDotGitPath != "" { + return providedDotGitPath, nil + } + dotGitPath, exists, err := fileutils.FindUpstream(".git", fileutils.Any) + if err != nil { + return "", err + } + if !exists { + return "", errorutils.CheckErrorf("Could not find .git") + } + return dotGitPath, nil +} + // Gets the vcs revision from the latest build in Artifactory. func getLatestVcsRevision(serverDetails *utilsconfig.ServerDetails, buildConfiguration *build.BuildConfiguration, vcsUrl string) (string, error) { buildInfo, err := getLatestBuildInfo(serverDetails, buildConfiguration) @@ -36,13 +145,13 @@ func getLatestVcsRevision(serverDetails *utilsconfig.ServerDetails, buildConfigu // Gets the vcs revision from the build in position "previousBuildPos" in Artifactory. previousBuildPos = 0 is the latest build. // previousBuildPos must be 0 or larger. -func getVcsFromPreviousBuild(serverDetails *utilsconfig.ServerDetails, buildConfiguration *build.BuildConfiguration, vcsUrl string, previousBuildPos int) (string, error) { - buildInfo, err := getPreviousBuild(serverDetails, buildConfiguration, previousBuildPos) +func getVcsFromPreviousBuild(serverDetails *utilsconfig.ServerDetails, buildConfiguration *build.BuildConfiguration, vcsUrl string) (string, error) { + buildInfo, err := getPreviousBuildsCommit(serverDetails, buildConfiguration) if err != nil { return "", err } - return getMatchingRevisionFromBuild(buildInfo, vcsUrl), nil + return getMatchingRevisionFromBuild(&buildInfo.BuildInfo, vcsUrl), nil } // Returns the vcs revision that matches th provided vcs url. @@ -85,7 +194,7 @@ func getLatestBuildInfo(serverDetails *utilsconfig.ServerDetails, buildConfigura // Returns the previous build in order provided by previousBuildPos. For previousBuildPos 0 the latest build is returned. // If previousBuildPos is not 0 or above, a general error will be returned. // If the build does not exist, or there are less previous build runs than requested, an empty build will be returned. -func getPreviousBuild(serverDetails *utilsconfig.ServerDetails, buildConfiguration *build.BuildConfiguration, previousBuildPos int) (*buildinfo.BuildInfo, error) { +func getPreviousBuild(serverDetails *utilsconfig.ServerDetails, buildConfiguration *build.BuildConfiguration, previousBuildPos int) (*buildinfo.PublishedBuildInfo, error) { if previousBuildPos < 0 { return nil, errorutils.CheckErrorf("invalid input for previous build position. Input must be a non negative number") } @@ -109,7 +218,7 @@ func getPreviousBuild(serverDetails *utilsconfig.ServerDetails, buildConfigurati } // Return if build not found, or not enough build runs were returned to match the requested previous position. if !found || len(runs.BuildsNumbers)-1 < previousBuildPos { - return &buildinfo.BuildInfo{}, nil + return &buildinfo.PublishedBuildInfo{}, nil } // Get matching build number. Build numbers are returned sorted, from latest to oldest. @@ -122,51 +231,100 @@ func getPreviousBuild(serverDetails *utilsconfig.ServerDetails, buildConfigurati } // If build was deleted between requests. if !found { - return &buildinfo.BuildInfo{}, nil + return &buildinfo.PublishedBuildInfo{}, nil } - return &publishedBuildInfo.BuildInfo, nil + return publishedBuildInfo, nil } -type GitLogDetails struct { - LogLimit int - PrettyFormat string - // Optional - DotGitPath string -} +// Retrieves the build information of the first build that has a different VCS commit hash compared to the latest build. +// Iterates through previous builds in descending order until it finds a build with a different commit hash. +// Returns an empty build info struct if no such build is found or if there are no previous builds available. +func getPreviousBuildsCommit(serverDetails *utilsconfig.ServerDetails, buildConfiguration *build.BuildConfiguration) (*buildinfo.PublishedBuildInfo, error) { + // Create services manager to get build-info from Artifactory. + sm, err := utils.CreateServiceManager(serverDetails, -1, 0, false) + if err != nil { + return nil, err + } -// Parses git commits from the last build's VCS revision. -// Calls git log with a custom format, and parses each line of the output with regexp. logRegExp is used to parse the log lines. -func ParseGitLogFromLastBuild(serverDetails *utilsconfig.ServerDetails, buildConfiguration *build.BuildConfiguration, gitDetails GitLogDetails, logRegExp *gofrogcmd.CmdOutputPattern) error { - vcsUrl, err := validateGitAndGetVcsUrl(&gitDetails) + buildName, err := buildConfiguration.GetBuildName() if err != nil { - return err + return nil, err } + projectKey := buildConfiguration.GetProject() + buildInfoParams := services.BuildInfoParams{BuildName: buildName, ProjectKey: projectKey} - // Get latest build's VCS revision from Artifactory. - lastVcsRevision, err := getLatestVcsRevision(serverDetails, buildConfiguration, vcsUrl) + runs, found, err := sm.GetBuildRuns(buildInfoParams) if err != nil { - return err + return nil, err + } + // Return if build not found, or not enough build runs were returned to match the requested previous position. + if !found || len(runs.BuildsNumbers) == 0 { + return &buildinfo.PublishedBuildInfo{}, nil } - return ParseGitLogFromLastVcsRevision(gitDetails, logRegExp, lastVcsRevision) -} -// Returns the git log output for the VCS revision for the previous build in position previousBuildPos. -// For previousBuildPos 0 the latest build is returned, for an input 1 the latest -1 is returned, etc. previousBuildPos must be 0 or above. -// Calls git log with a custom format, and returns the output as is. -// Return RevisionRangeError if revision isn't found (due to git history modification). -func GetPlainGitLogFromPreviousBuild(serverDetails *utilsconfig.ServerDetails, buildConfiguration *build.BuildConfiguration, gitDetails GitLogDetails, previousBuildPos int) (string, error) { - vcsUrl, err := validateGitAndGetVcsUrl(&gitDetails) + // Take the first log to get the reference for the first builds commit + lastBuildInfoParams := services.BuildInfoParams{BuildName: buildName, ProjectKey: projectKey} + lastBuildInfoParams.BuildNumber = strings.TrimPrefix(runs.BuildsNumbers[0].Uri, "/") + lastPublishedBuildInfo, found, err := sm.GetBuildInfo(lastBuildInfoParams) if err != nil { - return "", err + return nil, err + } + // If build was deleted between requests. + if !found { + return &buildinfo.PublishedBuildInfo{}, nil + } + for _, run := range runs.BuildsNumbers { + buildInfoParams.BuildNumber = strings.TrimPrefix(run.Uri, "/") + + publishedBuildInfo, found, err := sm.GetBuildInfo(buildInfoParams) + if err != nil { + return nil, err + } + // If build was deleted between requests. + if !found { + return &buildinfo.PublishedBuildInfo{}, nil + } + // If the commit hash is different from the last build, return the build info + if publishedBuildInfo.BuildInfo.VcsList[0].Revision != lastPublishedBuildInfo.BuildInfo.VcsList[0].Revision { + return publishedBuildInfo, nil + } } + return nil, errors.New("no previous builds commit has found") +} - lastVcsRevision, err := getVcsFromPreviousBuild(serverDetails, buildConfiguration, vcsUrl, previousBuildPos) +func convertToUiLink(info *buildinfo.PublishedBuildInfo) (string, error) { + datetime, err := utils2.ParseIsoTimestamp(info.BuildInfo.Started) if err != nil { return "", err } + epochMillis := strconv.FormatInt(datetime.UnixNano()/1_000_000, 10) + re := regexp.MustCompile(`(https://.+?)/artifactory/api/build/([^/]+)/([^?]+)(\?.+)?`) + matches := re.FindStringSubmatch(info.Uri) - return getPlainGitLogFromLastVcsRevision(gitDetails, lastVcsRevision) + if len(matches) < 4 { + return "", errors.New("invalid API URL format") + } + + baseUrl := matches[1] + buildName := matches[2] + buildNumber := matches[3] + queryParams := "" + if len(matches) == 5 { + queryParams = matches[4] // Preserve query parameters if they exist + } + + // Construct the UI-friendly URL + uiUrl := strings.Join([]string{ + baseUrl, + "ui/builds", + buildName, + buildNumber, + epochMillis, + "Evidence" + queryParams, + }, "/") + + return uiUrl, nil } // Validates git is in path, and returns the VCS url by searching in the .git directory. @@ -201,39 +359,6 @@ func prepareGitLogCommand(gitDetails GitLogDetails, lastVcsRevision string) (log return } -// Parses git log line by line, using the parser provided in logRegExp. -// Git log is parsed from lastVcsRevision to HEAD. -func ParseGitLogFromLastVcsRevision(gitDetails GitLogDetails, logRegExp *gofrogcmd.CmdOutputPattern, lastVcsRevision string) (err error) { - logCmd, cleanupFunc, err := prepareGitLogCommand(gitDetails, lastVcsRevision) - defer func() { - if cleanupFunc != nil { - err = errors.Join(err, cleanupFunc()) - } - }() - - errRegExp, err := createErrRegExpHandler(lastVcsRevision) - if err != nil { - return err - } - - // Run git command. - _, _, exitOk, err := gofrogcmd.RunCmdWithOutputParser(logCmd, false, logRegExp, errRegExp) - if errorutils.CheckError(err) != nil { - var revisionRangeError RevisionRangeError - if errors.As(err, &revisionRangeError) { - // Revision not found in range. Ignore and return. - log.Info(err.Error()) - return nil - } - return err - } - if !exitOk { - // May happen when trying to run git log for non-existing revision. - err = errorutils.CheckErrorf("failed executing git log command") - } - return err -} - // Runs git log from lastVcsRevision to HEAD, using the provided format, and returns the output as is. // Return RevisionRangeError if revision isn't found. func getPlainGitLogFromLastVcsRevision(gitDetails GitLogDetails, lastVcsRevision string) (gitLog string, err error) { @@ -278,22 +403,6 @@ func getRevisionRangeError(lastVcsRevision string) error { return RevisionRangeError{ErrorMsg: errMsg} } -// Looks for the .git directory in the current directory and its parents. -func GetDotGit(providedDotGitPath string) (string, error) { - if providedDotGitPath != "" { - return providedDotGitPath, nil - } - dotGitPath, exists, err := fileutils.FindUpstream(".git", fileutils.Any) - if err != nil { - return "", err - } - if !exists { - return "", errorutils.CheckErrorf("Could not find .git") - } - return dotGitPath, nil - -} - // Gets vcs url from the .git directory. func getVcsUrl(dotGitPath string) (string, error) { gitManager := clientutils.NewGitManager(dotGitPath) diff --git a/evidence/cli/command_cli.go b/evidence/cli/command_cli.go index 992d62e..0506b72 100644 --- a/evidence/cli/command_cli.go +++ b/evidence/cli/command_cli.go @@ -3,6 +3,7 @@ package cli import ( "errors" "github.com/jfrog/jfrog-cli-artifactory/evidence/cli/docs/create" + jfrogArtClient "github.com/jfrog/jfrog-cli-artifactory/evidence/utils" commonCliUtils "github.com/jfrog/jfrog-cli-core/v2/common/cliutils" "github.com/jfrog/jfrog-cli-core/v2/common/commands" pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" @@ -11,6 +12,7 @@ import ( coreUtils "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" + "golang.org/x/exp/slices" "os" "strings" ) @@ -34,7 +36,7 @@ func createEvidence(ctx *components.Context) error { if err := validateCreateEvidenceCommonContext(ctx); err != nil { return err } - subject, err := getAndValidateSubject(ctx) + evidenceType, err := getAndValidateSubject(ctx) if err != nil { return err } @@ -43,21 +45,25 @@ func createEvidence(ctx *components.Context) error { return err } - var command EvidenceCommands - switch subject { - case subjectRepoPath: - command = NewEvidenceCustomCommand(ctx, execFunc) - case releaseBundle: - command = NewEvidenceReleaseBundleCommand(ctx, execFunc) - case buildName: - command = NewEvidenceBuildCommand(ctx, execFunc) - case packageName: - command = NewEvidencePackageCommand(ctx, execFunc) - default: - return errors.New("unsupported subject") + // Check for GitHub evidence type explicitly + if slices.Contains(evidenceType, typeFlag) || (slices.Contains(evidenceType, buildName) && slices.Contains(evidenceType, typeFlag)) { + return NewEvidenceGitHubCommand(ctx, execFunc).CreateEvidence(ctx, serverDetails) } - return command.CreateEvidence(ctx, serverDetails) + // Map evidence types to their corresponding command functions + evidenceCommands := map[string]func(*components.Context, execCommandFunc) EvidenceCommands{ + subjectRepoPath: NewEvidenceCustomCommand, + releaseBundle: NewEvidenceReleaseBundleCommand, + buildName: NewEvidenceBuildCommand, + packageName: NewEvidencePackageCommand, + } + + // Fetch command from map; return an error if not found + if commandFunc, exists := evidenceCommands[evidenceType[0]]; exists { + return commandFunc(ctx, execFunc).CreateEvidence(ctx, serverDetails) + } + + return errors.New("unsupported subject") } func validateCreateEvidenceCommonContext(ctx *components.Context) error { @@ -69,11 +75,11 @@ func validateCreateEvidenceCommonContext(ctx *components.Context) error { return pluginsCommon.WrongNumberOfArgumentsHandler(ctx) } - if !ctx.IsFlagSet(predicate) || assertValueProvided(ctx, predicate) != nil { + if (!ctx.IsFlagSet(predicate) || assertValueProvided(ctx, predicate) != nil) && !ctx.IsFlagSet(typeFlag) { return errorutils.CheckErrorf("'predicate' is a mandatory field for creating evidence: --%s", predicate) } - if !ctx.IsFlagSet(predicateType) || assertValueProvided(ctx, predicateType) != nil { + if (!ctx.IsFlagSet(predicateType) || assertValueProvided(ctx, predicateType) != nil) && !ctx.IsFlagSet(typeFlag) { return errorutils.CheckErrorf("'predicate-type' is a mandatory field for creating evidence: --%s", predicateType) } @@ -84,7 +90,6 @@ func validateCreateEvidenceCommonContext(ctx *components.Context) error { if !ctx.IsFlagSet(keyAlias) { setKeyAliasIfProvided(ctx, keyAlias) } - return nil } @@ -93,7 +98,7 @@ func ensureKeyExists(ctx *components.Context, key string) error { return nil } - signingKeyValue, _ := getEnvVariable(coreUtils.SigningKey) + signingKeyValue, _ := jfrogArtClient.GetEnvVariable(coreUtils.SigningKey) if signingKeyValue == "" { return errorutils.CheckErrorf("JFROG_CLI_SIGNING_KEY env variable or --%s flag must be provided when creating evidence", key) } @@ -102,13 +107,13 @@ func ensureKeyExists(ctx *components.Context, key string) error { } func setKeyAliasIfProvided(ctx *components.Context, keyAlias string) { - evdKeyAliasValue, _ := getEnvVariable(coreUtils.KeyAlias) + evdKeyAliasValue, _ := jfrogArtClient.GetEnvVariable(coreUtils.KeyAlias) if evdKeyAliasValue != "" { ctx.AddStringFlag(keyAlias, evdKeyAliasValue) } } -func getAndValidateSubject(ctx *components.Context) (string, error) { +func getAndValidateSubject(ctx *components.Context) ([]string, error) { var foundSubjects []string for _, key := range subjectTypes { if ctx.GetStringFlagValue(key) != "" { @@ -119,16 +124,16 @@ func getAndValidateSubject(ctx *components.Context) (string, error) { if len(foundSubjects) == 0 { // If we have no subject - we will try to create EVD on build if !attemptSetBuildNameAndNumber(ctx) { - return "", errorutils.CheckErrorf("subject must be one of the fields: [%s]", strings.Join(subjectTypes, ", ")) + return nil, errorutils.CheckErrorf("subject must be one of the fields: [%s]", strings.Join(subjectTypes, ", ")) } foundSubjects = append(foundSubjects, buildName) } - if err := validateFoundSubjects(foundSubjects); err != nil { - return "", err + if err := validateFoundSubjects(ctx, foundSubjects); err != nil { + return nil, err } - return foundSubjects[0], nil + return foundSubjects, nil } func attemptSetBuildNameAndNumber(ctx *components.Context) bool { @@ -151,7 +156,15 @@ func setBuildValue(ctx *components.Context, flag, envVar string) bool { return false } -func validateFoundSubjects(foundSubjects []string) error { +func validateFoundSubjects(ctx *components.Context, foundSubjects []string) error { + if slices.Contains(foundSubjects, typeFlag) && slices.Contains(foundSubjects, buildName) { + return nil + } + + if slices.Contains(foundSubjects, typeFlag) && attemptSetBuildNameAndNumber(ctx) { + return nil + } + if len(foundSubjects) > 1 { return errorutils.CheckErrorf("multiple subjects found: [%s]", strings.Join(foundSubjects, ", ")) } diff --git a/evidence/cli/command_github.go b/evidence/cli/command_github.go new file mode 100644 index 0000000..01a8c47 --- /dev/null +++ b/evidence/cli/command_github.go @@ -0,0 +1,47 @@ +package cli + +import ( + "github.com/jfrog/jfrog-cli-artifactory/evidence" + "github.com/jfrog/jfrog-cli-core/v2/plugins/components" + coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-client-go/utils/errorutils" +) + +type evidenceGitHubCommand struct { + ctx *components.Context + execute execCommandFunc +} + +func NewEvidenceGitHubCommand(ctx *components.Context, execute execCommandFunc) EvidenceCommands { + return &evidenceGitHubCommand{ + ctx: ctx, + execute: execute, + } +} + +func (ebc *evidenceGitHubCommand) CreateEvidence(ctx *components.Context, serverDetails *coreConfig.ServerDetails) error { + err := ebc.validateEvidenceBuildContext(ctx) + if err != nil { + return err + } + + createCmd := evidence.NewCreateGithub( + serverDetails, + ebc.ctx.GetStringFlagValue(predicate), + ebc.ctx.GetStringFlagValue(predicateType), + ebc.ctx.GetStringFlagValue(markdown), + ebc.ctx.GetStringFlagValue(key), + ebc.ctx.GetStringFlagValue(keyAlias), + ebc.ctx.GetStringFlagValue(project), + ebc.ctx.GetStringFlagValue(buildName), + ebc.ctx.GetStringFlagValue(buildNumber), + ebc.ctx.GetStringFlagValue(typeFlag)) + return ebc.execute(createCmd) +} + +func (ebc *evidenceGitHubCommand) validateEvidenceBuildContext(ctx *components.Context) error { + if !ctx.IsFlagSet(buildNumber) || assertValueProvided(ctx, buildNumber) != nil { + return errorutils.CheckErrorf("--%s is a mandatory field for creating a Release Bundle evidence", buildNumber) + } + return nil +} diff --git a/evidence/cli/flags.go b/evidence/cli/flags.go index 13afdc6..c87e845 100644 --- a/evidence/cli/flags.go +++ b/evidence/cli/flags.go @@ -26,6 +26,7 @@ const ( packageName = "package-name" packageVersion = "package-version" packageRepoName = "package-repo-name" + typeFlag = "type" // Unique evidence flags predicate = "predicate" @@ -53,9 +54,10 @@ var flagsMap = map[string]components.Flag{ packageName: components.NewStringFlag(packageName, "Package name.", func(f *components.StringFlag) { f.Mandatory = false }), packageVersion: components.NewStringFlag(packageVersion, "Package version.", func(f *components.StringFlag) { f.Mandatory = false }), packageRepoName: components.NewStringFlag(packageRepoName, "Package repository Name.", func(f *components.StringFlag) { f.Mandatory = false }), + typeFlag: components.NewStringFlag(typeFlag, "Type can contain 'gh-commiter' value.", func(f *components.StringFlag) { f.Mandatory = false }), - predicate: components.NewStringFlag(predicate, "Path to the predicate, arbitrary JSON.", func(f *components.StringFlag) { f.Mandatory = true }), - predicateType: components.NewStringFlag(predicateType, "Type of the predicate.", func(f *components.StringFlag) { f.Mandatory = true }), + predicate: components.NewStringFlag(predicate, "Path to the predicate, arbitrary JSON.", func(f *components.StringFlag) { f.Mandatory = false }), + predicateType: components.NewStringFlag(predicateType, "Type of the predicate.", func(f *components.StringFlag) { f.Mandatory = false }), markdown: components.NewStringFlag(markdown, "Markdown of the predicate.", func(f *components.StringFlag) { f.Mandatory = false }), subjectRepoPath: components.NewStringFlag(subjectRepoPath, "Full path to some subject' location.", func(f *components.StringFlag) { f.Mandatory = false }), subjectSha256: components.NewStringFlag(subjectSha256, "Subject checksum sha256.", func(f *components.StringFlag) { f.Mandatory = false }), @@ -77,6 +79,7 @@ var commandFlags = map[string][]string{ packageName, packageVersion, packageRepoName, + typeFlag, predicate, predicateType, markdown, diff --git a/evidence/cli/utils.go b/evidence/cli/utils.go index b1db57d..9f40bcf 100644 --- a/evidence/cli/utils.go +++ b/evidence/cli/utils.go @@ -1,9 +1,7 @@ package cli import ( - "fmt" "github.com/jfrog/jfrog-cli-core/v2/common/commands" - "os" ) type execCommandFunc func(command commands.Command) error @@ -17,11 +15,5 @@ var subjectTypes = []string{ releaseBundle, buildName, packageName, -} - -func getEnvVariable(envVarName string) (string, error) { - if key, exists := os.LookupEnv(envVarName); exists { - return key, nil - } - return "", fmt.Errorf("'%s' field wasn't provided.", envVarName) + typeFlag, } diff --git a/evidence/create_base.go b/evidence/create_base.go index 5f5f090..473f50c 100644 --- a/evidence/create_base.go +++ b/evidence/create_base.go @@ -25,6 +25,7 @@ type createEvidenceBase struct { markdownFilePath string key string keyId string + flagType FlagType } func (c *createEvidenceBase) createEnvelope(subject, subjectSha256 string) ([]byte, error) { @@ -38,6 +39,26 @@ func (c *createEvidenceBase) createEnvelope(subject, subjectSha256 string) ([]by return nil, err } + envelopeBytes, err := json.Marshal(signedEnvelope) + if err != nil { + return nil, err + } + return envelopeBytes, nil +} + +func (c *createEvidenceBase) createEnvelopeWithPredicateAndPredicateType(subject, + subjectSha256, predicateType string, predicate []byte) ([]byte, error) { + statementJson, err := c.buildIntotoStatementJsonWithPredicateAndPredicateType(subject, + subjectSha256, predicateType, predicate) + if err != nil { + return nil, err + } + + signedEnvelope, err := createAndSignEnvelope(statementJson, c.key, c.keyId) + if err != nil { + return nil, err + } + // Encode signedEnvelope into a byte slice envelopeBytes, err := json.Marshal(signedEnvelope) if err != nil { @@ -76,6 +97,30 @@ func (c *createEvidenceBase) buildIntotoStatementJson(subject, subjectSha256 str return statementJson, nil } +func (c *createEvidenceBase) buildIntotoStatementJsonWithPredicateAndPredicateType(subject, subjectSha256, predicateType string, predicate []byte) ([]byte, error) { + artifactoryClient, err := c.createArtifactoryClient() + if err != nil { + return nil, err + } + + statement := intoto.NewStatement(predicate, predicateType, c.serverDetails.User) + err = c.setMarkdown(statement) + if err != nil { + return nil, err + } + + err = statement.SetSubject(artifactoryClient, subject, subjectSha256) + if err != nil { + return nil, err + } + statementJson, err := statement.Marshal() + if err != nil { + log.Error("failed marshaling statement json file", err) + return nil, err + } + return statementJson, nil +} + func (c *createEvidenceBase) setMarkdown(statement *intoto.Statement) error { if c.markdownFilePath != "" { if !strings.HasSuffix(c.markdownFilePath, ".md") { diff --git a/evidence/create_github.go b/evidence/create_github.go new file mode 100644 index 0000000..7bc3bbc --- /dev/null +++ b/evidence/create_github.go @@ -0,0 +1,283 @@ +package evidence + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "github.com/jfrog/froggit-go/vcsclient" + "github.com/jfrog/froggit-go/vcsutils" + artifactoryUtils "github.com/jfrog/jfrog-cli-artifactory/artifactory/utils" + "github.com/jfrog/jfrog-cli-artifactory/evidence/model" + "github.com/jfrog/jfrog-cli-artifactory/evidence/utils" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/commandsummary" + "github.com/jfrog/jfrog-cli-core/v2/common/build" + coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-client-go/artifactory" + "github.com/jfrog/jfrog-client-go/utils/log" + "os" + "regexp" + "strings" +) + +type FlagType string + +const ( + FlagTypeCommitterReviewer FlagType = "gh-commiter" + FlagTypeOther FlagType = "other" +) + +const ghDefaultPredicateType = "https://jfrog.com/evidence/gh-commiter/v1" + +const gitFormat = `format:'{"commit":"%H","abbreviated_commit":"%h","tree":"%T","abbreviated_tree":"%t","parent":"%P","abbreviated_parent":"%p","subject":"%s","sanitized_subject_line":"%f","author":{"name":"%aN","email":"%aE","date":"%aD"},"commiter":{"name":"%cN","email":"%cE","date":"%cD"}}'` + +type createGitHubEvidence struct { + createEvidenceBase + project string + buildName string + buildNumber string +} + +func NewCreateGithub(serverDetails *coreConfig.ServerDetails, predicateFilePath, predicateType, markdownFilePath, key, keyId, project, buildName, buildNumber, typeFlag string) Command { + flagType := getFlagType(typeFlag) + return &createGitHubEvidence{ + createEvidenceBase: createEvidenceBase{ + serverDetails: serverDetails, + predicateFilePath: predicateFilePath, + predicateType: predicateType, + markdownFilePath: markdownFilePath, + key: key, + keyId: keyId, + flagType: flagType, + }, + project: project, + buildName: buildName, + buildNumber: buildNumber, + } +} + +func getFlagType(typeFlag string) FlagType { + var flagType FlagType + if typeFlag == "gh-commiter" { + flagType = FlagTypeCommitterReviewer + } else { + flagType = FlagTypeOther + } + return flagType +} + +func (c *createGitHubEvidence) CommandName() string { + return "create-github-evidence" +} + +func (c *createGitHubEvidence) ServerDetails() (*coreConfig.ServerDetails, error) { + return c.serverDetails, nil +} + +func (c *createGitHubEvidence) Run() error { + if !isRunningUnderGitHubAction() { + return errors.New("this command is intended to be run under GitHub Actions") + } + evidencePredicate, err := c.committerReviewerEvidence() + if err != nil { + return err + } + + artifactoryClient, err := c.createArtifactoryClient() + if err != nil { + log.Error("failed to create Artifactory client", err) + return err + } + subject, sha256, err := c.buildBuildInfoSubjectPath(artifactoryClient) + if err != nil { + return err + } + envelope, err := c.createEnvelopeWithPredicateAndPredicateType(subject, + sha256, ghDefaultPredicateType, evidencePredicate) + if err != nil { + return err + } + err = c.uploadEvidence(envelope, subject) + if err != nil { + return err + } + + err = c.recordEvidenceSummaryData(evidencePredicate, subject, sha256) + if err != nil { + return err + } + + return nil +} + +func (c *createGitHubEvidence) buildBuildInfoSubjectPath(artifactoryClient artifactory.ArtifactoryServicesManager) (string, string, error) { + timestamp, err := getBuildLatestTimestamp(c.buildName, c.buildNumber, c.project, artifactoryClient) + if err != nil { + return "", "", err + } + + repoKey := buildBuildInfoRepoKey(c.project) + buildInfoPath := buildBuildInfoPath(repoKey, c.buildName, c.buildNumber, timestamp) + buildInfoChecksum, err := getBuildInfoPathChecksum(buildInfoPath, artifactoryClient) + if err != nil { + return "", "", err + } + return buildInfoPath, buildInfoChecksum, nil +} + +func isRunningUnderGitHubAction() bool { + return os.Getenv("GITHUB_ACTIONS") == "true" +} + +// This function will print the markdown to GH +func (c *createGitHubEvidence) recordEvidenceSummaryData(evidence []byte, subject string, subjectSha256 string) error { + commandSummary, err := commandsummary.NewBuildInfoSummary() + if err != nil { + return err + } + + gitLogModel, err := marshalEvidenceToGitLogEntryView(evidence) + if err != nil { + return err + } + link, err := c.getLastBuildLink() + if err != nil { + return err + } + gitLogModel.Link = link + gitLogModel.Artifact.Path = subject + gitLogModel.Artifact.Sha256 = subjectSha256 + gitLogModel.Artifact.Name = c.buildName + + err = commandSummary.RecordWithIndex(gitLogModel, commandsummary.Evidence) + if err != nil { + return err + } + return nil +} + +func (c *createGitHubEvidence) getLastBuildLink() (string, error) { + buildConfiguration := new(build.BuildConfiguration) + buildConfiguration.SetBuildName(c.buildName).SetBuildNumber(c.buildName).SetProject(c.project) + link, err := artifactoryUtils.GetLastBuildLink(c.serverDetails, buildConfiguration) + if err != nil { + return "", err + } + return link, nil +} + +func marshalEvidenceToGitLogEntryView(evidence []byte) (*model.GitLogEntryView, error) { + var gitLogEntryView model.GitLogEntryView + err := json.Unmarshal(evidence, &gitLogEntryView.Data) + if err != nil { + return nil, err + } + return &gitLogEntryView, nil +} + +func (c *createGitHubEvidence) committerReviewerEvidence() ([]byte, error) { + if c.createEvidenceBase.flagType != FlagTypeCommitterReviewer { + return nil, errors.New("flag type must be gh-commiter") + } + + createBuildConfiguration := c.createBuildConfiguration() + gitDetails := artifactoryUtils.GitLogDetails{LogLimit: 100, PrettyFormat: gitFormat} + committerEvidence, err := getGitCommitInfo(c.serverDetails, createBuildConfiguration, gitDetails) + if err != nil { + return nil, err + } + return committerEvidence, nil +} + +func (c *createGitHubEvidence) createBuildConfiguration() *build.BuildConfiguration { + buildConfiguration := new(build.BuildConfiguration) + buildConfiguration.SetBuildName(c.buildName).SetBuildNumber(c.buildNumber).SetProject(c.project) + return buildConfiguration +} + +func getGitCommitInfo(serverDetails *coreConfig.ServerDetails, createBuildConfiguration *build.BuildConfiguration, gitDetails artifactoryUtils.GitLogDetails) ([]byte, error) { + owner, repository, err := gitHubRepositoryDetails() + if err != nil { + return nil, err + } + + entries, err := getGitCommitEntries(serverDetails, createBuildConfiguration, gitDetails) + if err != nil { + return nil, err + } + + ghToken, err := utils.GetEnvVariable("JF_GIT_TOKEN") + if err != nil { + return nil, err + } + + client, err := vcsclient.NewClientBuilder(vcsutils.GitHub).Token(ghToken).Build() + if err != nil { + return nil, err + } + + for i := range entries { + prMetadata, err := client.ListPullRequestsAssociatedWithCommit(context.Background(), owner, repository, entries[i].Commit) + if err != nil { + log.Warn(fmt.Sprintf("Failed to get PR metadata for commit: %s, error: %v", entries[i].Commit, err)) + continue + } + + if len(prMetadata) == 0 { + log.Info(fmt.Sprintf("No PR metadata found for commit: %s", entries[i].Commit)) + entries[i].PRreviewer = []vcsclient.PullRequestReviewDetails{} + continue + } + + prReviewer, err := client.ListPullRequestReviews(context.Background(), owner, repository, int(prMetadata[0].ID)) + if err != nil { + log.Warn(fmt.Sprintf("Failed to get PR reviews for PR ID %d: %v", prMetadata[0].ID, err)) + entries[i].PRreviewer = []vcsclient.PullRequestReviewDetails{} + continue + } + + entries[i].PRreviewer = make([]vcsclient.PullRequestReviewDetails, len(prReviewer)) + copy(entries[i].PRreviewer, prReviewer) + } + + out, err := json.MarshalIndent(entries, "", " ") + if err != nil { + return nil, err + } + return out, nil +} + +func getGitCommitEntries(serverDetails *coreConfig.ServerDetails, createBuildConfiguration *build.BuildConfiguration, gitDetails artifactoryUtils.GitLogDetails) ([]model.GitLogEntry, error) { + fullLog, err := artifactoryUtils.GetPlainGitLogFromPreviousBuild(serverDetails, createBuildConfiguration, gitDetails) + if err != nil { + return nil, err + } + + re := regexp.MustCompile(`'(\{.*?\})'`) + matches := re.FindAllStringSubmatch(fullLog, -1) + + var entries []model.GitLogEntry + for _, m := range matches { + jsonText := m[1] // The captured group is the JSON object + var entry model.GitLogEntry + if err := json.Unmarshal([]byte(jsonText), &entry); err != nil { + return nil, err + } + entries = append(entries, entry) + } + return entries, nil +} + +func gitHubRepositoryDetails() (string, string, error) { + githubRepo := os.Getenv("GITHUB_REPOSITORY") // Format: "owner/repository" + if githubRepo == "" { + return "", "", fmt.Errorf("GITHUB_REPOSITORY environment variable is not set") + } + + parts := strings.Split(githubRepo, "/") + if len(parts) != 2 { + return "", "", fmt.Errorf("invalid GITHUB_REPOSITORY format: %s", githubRepo) + } + owner, repository := parts[0], parts[1] + return owner, repository, nil +} diff --git a/evidence/create_github_test.go b/evidence/create_github_test.go new file mode 100644 index 0000000..d6c1112 --- /dev/null +++ b/evidence/create_github_test.go @@ -0,0 +1,134 @@ +package evidence + +import ( + gofrogcmd "github.com/jfrog/gofrog/io" + artifactoryUtils "github.com/jfrog/jfrog-cli-artifactory/artifactory/utils" + "os" + "testing" + + "github.com/jfrog/jfrog-cli-core/v2/common/build" + coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +// MockBuildAndVcsDetails is a mock implementation of the BuildAndVcsDetails interface +type MockBuildAndVcsDetails struct { + mock.Mock +} + +func (m *MockBuildAndVcsDetails) ParseGitLogFromLastVcsRevision(gitDetails artifactoryUtils.GitLogDetails, logRegExp *gofrogcmd.CmdOutputPattern, lastVcsRevision string) error { + args := m.Called(gitDetails, logRegExp, lastVcsRevision) + return args.Error(0) +} + +func (m *MockBuildAndVcsDetails) GetPlainGitLogFromPreviousBuild(serverDetails *coreConfig.ServerDetails, buildConfiguration *build.BuildConfiguration, gitDetails artifactoryUtils.GitLogDetails) (string, error) { + args := m.Called(serverDetails, buildConfiguration, gitDetails) + return args.String(0), args.Error(1) +} + +func (m *MockBuildAndVcsDetails) GetLastBuildLink(serverDetails *coreConfig.ServerDetails, buildConfiguration *build.BuildConfiguration) (string, error) { + args := m.Called(serverDetails, buildConfiguration) + return args.String(0), args.Error(1) +} + +func TestNewCreateGithub(t *testing.T) { + serverDetails := &coreConfig.ServerDetails{} + command := NewCreateGithub(serverDetails, "path/to/predicate.json", "predicateType", "path/to/markdown.md", "key", "keyId", "myProject", "myBuild", "123", "gh-commiter") + + assert.NotNil(t, command) + + cgEvidence, ok := command.(*createGitHubEvidence) + assert.True(t, ok, "Expected command to be of type *createGitHubEvidence") + + assert.Equal(t, "myProject", cgEvidence.project) + assert.Equal(t, "myBuild", cgEvidence.buildName) + assert.Equal(t, "123", cgEvidence.buildNumber) + assert.Equal(t, FlagTypeCommitterReviewer, cgEvidence.flagType) +} + +func TestIsRunningUnderGitHubAction(t *testing.T) { + _ = os.Setenv("GITHUB_ACTIONS", "true") + defer func() { + _ = os.Unsetenv("GITHUB_ACTIONS") + }() + assert.True(t, isRunningUnderGitHubAction()) + + _ = os.Setenv("GITHUB_ACTIONS", "false") + defer func() { + _ = os.Unsetenv("GITHUB_ACTIONS") + }() + assert.False(t, isRunningUnderGitHubAction()) +} + +func TestGetFlagType(t *testing.T) { + assert.Equal(t, FlagTypeCommitterReviewer, getFlagType("gh-commiter")) + assert.Equal(t, FlagTypeOther, getFlagType("random")) +} + +func TestCommitterReviewerEvidence_FlagTypeMismatch(t *testing.T) { + command := &createGitHubEvidence{ + createEvidenceBase: createEvidenceBase{flagType: FlagTypeOther}, + } + + _, err := command.committerReviewerEvidence() + assert.Error(t, err) + assert.Equal(t, "flag type must be gh-commiter", err.Error()) +} + +func TestBuildAndVcsDetailsMock(t *testing.T) { + mockBuildVcs := new(MockBuildAndVcsDetails) + + // Define expected return values + mockBuildVcs.On("GetLastBuildLink", mock.Anything, mock.Anything).Return("http://mocked-url.com", nil) + mockBuildVcs.On("GetPlainGitLogFromPreviousBuild", mock.Anything, mock.Anything, mock.Anything).Return("mocked git log", nil) + + // Call the method under test + url, err := mockBuildVcs.GetLastBuildLink(nil, nil) + assert.NoError(t, err) + assert.Equal(t, "http://mocked-url.com", url) + + gitLog, err := mockBuildVcs.GetPlainGitLogFromPreviousBuild(nil, nil, artifactoryUtils.GitLogDetails{}) + assert.NoError(t, err) + assert.Equal(t, "mocked git log", gitLog) + + // Assert that the expected calls were made + mockBuildVcs.AssertExpectations(t) +} + +// Test gitHubRepositoryDetails +func TestGitHubRepositoryDetails(t *testing.T) { + _ = os.Setenv("GITHUB_REPOSITORY", "jfrog/myrepo") + defer func() { + _ = os.Unsetenv("GITHUB_REPOSITORY") // Remove the environment variable + }() + owner, repo, err := gitHubRepositoryDetails() + + assert.NoError(t, err) + assert.Equal(t, "jfrog", owner) + assert.Equal(t, "myrepo", repo) +} + +// Test gitHubRepositoryDetails when env var is missing +func TestGitHubRepositoryDetails_MissingEnvVar(t *testing.T) { + _ = os.Setenv("GITHUB_REPOSITORY", "") + defer func() { + _ = os.Unsetenv("GITHUB_REPOSITORY") // Remove the environment variable + }() + + _, _, err := gitHubRepositoryDetails() + assert.Error(t, err) + assert.Equal(t, "GITHUB_REPOSITORY environment variable is not set", err.Error()) +} + +// Test gitHubRepositoryDetails with invalid format +func TestGitHubRepositoryDetails_InvalidFormat(t *testing.T) { + _ = os.Setenv("GITHUB_REPOSITORY", "jfrog") + defer func() { + _ = os.Unsetenv("GITHUB_REPOSITORY") // Remove the environment variable + }() + + _, _, err := gitHubRepositoryDetails() + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid GITHUB_REPOSITORY format") +} diff --git a/evidence/model/github.go b/evidence/model/github.go new file mode 100644 index 0000000..90b44a8 --- /dev/null +++ b/evidence/model/github.go @@ -0,0 +1,25 @@ +package model + +import "github.com/jfrog/froggit-go/vcsclient" + +type GitLogEntry struct { + Commit string `json:"commit,omitempty"` + AbbreviatedCommit string `json:"abbreviated_commit,omitempty"` + Tree string `json:"tree,omitempty"` + AbbreviatedTree string `json:"abbreviated_tree,omitempty"` + Parent string `json:"parent,omitempty"` + AbbreviatedParent string `json:"abbreviated_parent,omitempty"` + Subject string `json:"subject,omitempty"` + SanitizedSubject string `json:"sanitized_subject_line,omitempty"` + Author struct { + Name string `json:"name,omitempty"` + Email string `json:"email,omitempty"` + Date string `json:"date,omitempty"` + } `json:"author,omitempty"` + Commiter struct { + Name string `json:"name,omitempty"` + Email string `json:"email,omitempty"` + Date string `json:"date,omitempty"` + } `json:"commiter,omitempty"` + PRreviewer []vcsclient.PullRequestReviewDetails `json:"pr_reviewer,omitempty"` +} diff --git a/evidence/model/github_view.go b/evidence/model/github_view.go new file mode 100644 index 0000000..d5a1537 --- /dev/null +++ b/evidence/model/github_view.go @@ -0,0 +1,9 @@ +package model + +import "github.com/jfrog/build-info-go/entities" + +type GitLogEntryView struct { + Data []GitLogEntry `json:"data"` + Link string `json:"link"` + Artifact entities.Artifact `json:"artifact"` +} diff --git a/evidence/utils/environment.go b/evidence/utils/environment.go new file mode 100644 index 0000000..e4d15bb --- /dev/null +++ b/evidence/utils/environment.go @@ -0,0 +1,13 @@ +package utils + +import ( + "fmt" + "os" +) + +func GetEnvVariable(envVarName string) (string, error) { + if key, exists := os.LookupEnv(envVarName); exists { + return key, nil + } + return "", fmt.Errorf("'%s' field wasn't provided.", envVarName) +} diff --git a/go.mod b/go.mod index ae289b6..580ebab 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,13 @@ module github.com/jfrog/jfrog-cli-artifactory go 1.23.4 +toolchain go1.23.5 + require ( github.com/c-bata/go-prompt v0.2.5 github.com/forPelevin/gomoji v1.2.0 github.com/jfrog/build-info-go v1.10.9 + github.com/jfrog/froggit-go v1.16.2 github.com/jfrog/gofrog v1.7.6 github.com/jfrog/jfrog-cli-core/v2 v2.58.0 github.com/jfrog/jfrog-client-go v1.50.0 @@ -41,6 +44,7 @@ require ( github.com/dsnet/compress v0.0.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gfleury/go-bitbucket-v1 v0.0.0-20230825095122-9bc1711434ab // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect github.com/go-git/go-git/v5 v5.13.2 // indirect @@ -51,9 +55,14 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-github/v56 v56.0.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gookit/color v1.5.4 // indirect + github.com/grokify/mogo v0.64.12 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect @@ -64,12 +73,14 @@ require ( github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/klauspost/pgzip v1.2.6 // indirect + github.com/ktrysmt/go-bitbucket v0.9.80 // indirect github.com/magiconair/properties v1.8.9 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-tty v0.0.3 // indirect + github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -92,18 +103,23 @@ require ( github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/ulikunitz/xz v0.5.12 // indirect github.com/vbauerster/mpb/v8 v8.9.1 // indirect + github.com/xanzy/go-gitlab v0.110.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/term v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect + golang.org/x/time v0.7.0 // indirect + golang.org/x/tools v0.29.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect @@ -119,12 +135,11 @@ require ( sigs.k8s.io/yaml v1.4.0 // indirect ) -//replace github.com/jfrog/jfrog-cli-core/v2 => github.com/oshratZairi/jfrog-cli-core/v2 v2.56.9-0.20241127142944-b39d0cc8f1c1 -//replace github.com/jfrog/jfrog-cli-core/v2 => github.com/oshratZairi/jfrog-cli-core/v2 v2.31.1-0.20241211104546-3e12a85de116 +//replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20250130104846-27e495de291e +replace github.com/jfrog/jfrog-cli-core/v2 => github.com/EyalDelarea/jfrog-cli-core/v2 v2.0.0-20250206084646-819832cab1e1 -replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20250212021126-e5223ab616af +//replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20250212021126-e5223ab616af -// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20250126110945-81abbdde452f +replace github.com/jfrog/froggit-go => github.com/EyalDelarea/froggit-go v1.6.1-0.20250204105801-761f8d527d7f -//replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240811150357-12a9330a2d67 -//replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20240811142930-ab9715567376 +replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20250203111011-4ff16d3d42be diff --git a/go.sum b/go.sum index 3088017..f04b617 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,13 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/CycloneDX/cyclonedx-go v0.9.2 h1:688QHn2X/5nRezKe2ueIVCt+NRqf7fl3AVQk+vaFcIo= github.com/CycloneDX/cyclonedx-go v0.9.2/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6SbM2ZuMIgq86Jg= +github.com/EyalDelarea/froggit-go v1.6.1-0.20250204105801-761f8d527d7f/go.mod h1:HvDkfFfJwIdsXFdqaB+utvD2cLDRmaC3kF8otYb6Chw= +github.com/EyalDelarea/jfrog-cli-core/v2 v2.0.0-20250206084646-819832cab1e1/go.mod h1:Hx2houXADsNv0eRh4w5XCS2uSsPNPn1OmiSDdwGFB7g= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= @@ -41,6 +45,7 @@ github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZ github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -60,6 +65,7 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gfleury/go-bitbucket-v1 v0.0.0-20230825095122-9bc1711434ab/go.mod h1:IqOZzks2wlWCIai0esXnZPdPwxF2yOz0HcCYw5I4pCg= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -84,20 +90,28 @@ github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQg github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v56 v56.0.0/go.mod h1:D8cdcX98YWJvi7TLo7zM4/h8ZTx6u6fwGEkCdisopo0= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= +github.com/grokify/mogo v0.64.12/go.mod h1:lDhfYIiOhJo7C2U3aL00PlUU9gLvmTONi4MdIWoGmGM= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= @@ -108,6 +122,7 @@ github.com/jedib0t/go-pretty/v6 v6.6.5 h1:9PgMJOVBedpgYLI56jQRJYqngxYAAzfEUua+3N github.com/jedib0t/go-pretty/v6 v6.6.5/go.mod h1:Uq/HrbhuFty5WSVNfjpQQe47x16RwVGXIveNGEyGtHs= github.com/jfrog/archiver/v3 v3.6.1 h1:LOxnkw9pOn45DzCbZNFV6K0+6dCsQ0L8mR3ZcujO5eI= github.com/jfrog/archiver/v3 v3.6.1/go.mod h1:VgR+3WZS4N+i9FaDwLZbq+jeU4B4zctXL+gL4EMzfLw= +github.com/jfrog/build-info-go v1.8.9-0.20250203111011-4ff16d3d42be/go.mod h1:JcISnovFXKx3wWf3p1fcMmlPdt6adxScXvoJN4WXqIE= github.com/jfrog/build-info-go v1.10.9 h1:mdJ+wlLw2ReFsqC7rifJVlRYLEqYk38uXDYAOZASuGE= github.com/jfrog/build-info-go v1.10.9/go.mod h1:JcISnovFXKx3wWf3p1fcMmlPdt6adxScXvoJN4WXqIE= github.com/jfrog/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s= @@ -118,6 +133,8 @@ github.com/jfrog/jfrog-client-go v1.50.0 h1:t7v/zpLkPomHR6ZjVbPQ1WPQJd9IFKESK9Tt github.com/jfrog/jfrog-client-go v1.50.0/go.mod h1:xHxwKBjPSUBd/FyCWgusfHmSWKUZTkfOZkTmntC2F5Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -131,16 +148,20 @@ github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/ktrysmt/go-bitbucket v0.9.80/go.mod h1:b8ogWEGxQMWoeFnT1ZE4aHIPGindI+9z/zAW/OVFjk0= github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -157,8 +178,10 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= +github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0/go.mod h1:mDunUZ1IUJdJIRHvFb+LPBUtxe3AYB5MI6BMXNg8194= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -180,6 +203,7 @@ github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= @@ -190,6 +214,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -242,6 +268,7 @@ github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ= github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po= github.com/vbauerster/mpb/v8 v8.9.1 h1:LH5R3lXPfE2e3lIGxN7WNWv3Hl5nWO6LRi2B0L0ERHw= github.com/vbauerster/mpb/v8 v8.9.1/go.mod h1:4XMvznPh8nfe2NpnDo1QTPvW9MVkUhbG90mPWvmOzcQ= +github.com/xanzy/go-gitlab v0.110.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= @@ -258,6 +285,7 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= @@ -269,25 +297,45 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc= golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -307,34 +355,55 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=