Skip to content

Commit 5f4344d

Browse files
authored
Artifactory command migration (#1370)
1 parent 7a3cb0b commit 5f4344d

File tree

5 files changed

+298
-4
lines changed

5 files changed

+298
-4
lines changed

common/cliutils/spec.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
speccore "github.com/jfrog/jfrog-cli-core/v2/common/spec"
55
"github.com/jfrog/jfrog-cli-core/v2/plugins/components"
66
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
7+
"strconv"
78
"strings"
89
)
910

@@ -62,7 +63,12 @@ func OverrideFieldsIfSet(spec *speccore.File, c *components.Context) {
6263
// If `fieldName` exist in the cli args, read it to `field` as a string.
6364
func overrideStringIfSet(field *string, c *components.Context, fieldName string) {
6465
if c.IsFlagSet(fieldName) {
65-
*field = c.GetStringFlagValue(fieldName)
66+
stringFlag := c.GetStringFlagValue(fieldName)
67+
if stringFlag != "" {
68+
*field = stringFlag
69+
return
70+
}
71+
*field = strconv.FormatBool(c.GetBoolFlagValue(fieldName))
6672
}
6773
}
6874

plugins/common/utils.go

Lines changed: 238 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
package common
22

33
import (
4+
"errors"
5+
commandUtils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils"
6+
artifactoryUtils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils"
7+
"github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/commandsummary"
8+
buildUtils "github.com/jfrog/jfrog-cli-core/v2/common/build"
9+
"github.com/jfrog/jfrog-cli-core/v2/common/cliutils/summary"
10+
coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config"
411
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
512
clientutils "github.com/jfrog/jfrog-client-go/utils"
613
"os"
14+
"github.com/jfrog/jfrog-client-go/utils/errorutils"
15+
"github.com/jfrog/jfrog-client-go/utils/io/content"
716
"sort"
817
"strconv"
918
"strings"
@@ -14,6 +23,16 @@ import (
1423
"golang.org/x/exp/slices"
1524
)
1625

26+
type DetailedSummaryRecord struct {
27+
Source string `json:"source,omitempty"`
28+
Target string `json:"target"`
29+
}
30+
31+
type ExtendedDetailedSummaryRecord struct {
32+
DetailedSummaryRecord
33+
Sha256 string `json:"sha256"`
34+
}
35+
1736
func GetStringsArrFlagValue(c *components.Context, flagName string) (resultArray []string) {
1837
if c.IsFlagSet(flagName) {
1938
resultArray = append(resultArray, strings.Split(c.GetStringFlagValue(flagName), ";")...)
@@ -57,6 +76,10 @@ func RunCmdWithDeprecationWarning(cmdName, oldSubcommand string, c *components.C
5776
return cmd(c)
5877
}
5978

79+
func GetThreadsCount(c *components.Context) (threads int, err error) {
80+
return cliutils.GetThreadsCount(c.GetStringFlagValue("threads"))
81+
}
82+
6083
func GetPrintCurrentCmdHelp(c *components.Context) func() error {
6184
return func() error {
6285
return c.PrintCommandHelp(c.CommandName)
@@ -122,6 +145,48 @@ func getCiValue() bool {
122145
return ci
123146
}
124147

148+
func CreateArtifactoryDetailsByFlags(c *components.Context) (*coreConfig.ServerDetails, error) {
149+
artDetails, err := CreateServerDetailsWithConfigOffer(c, false, cliutils.Rt)
150+
if err != nil {
151+
return nil, err
152+
}
153+
if artDetails.ArtifactoryUrl == "" {
154+
return nil, errors.New("no JFrog Artifactory URL specified, either via the --url flag or as part of the server configuration")
155+
}
156+
return artDetails, nil
157+
}
158+
159+
// Returns build configuration struct using the options provided by the user.
160+
// Any empty configuration could be later overridden by environment variables if set.
161+
func CreateBuildConfigurationWithModule(c *components.Context) (buildConfigConfiguration *buildUtils.BuildConfiguration, err error) {
162+
buildConfigConfiguration = new(buildUtils.BuildConfiguration)
163+
err = buildConfigConfiguration.SetBuildName(c.GetStringFlagValue("build-name")).SetBuildNumber(c.GetStringFlagValue("build-number")).
164+
SetProject(c.GetStringFlagValue("project")).SetModule(c.GetStringFlagValue("module")).ValidateBuildAndModuleParams()
165+
return
166+
}
167+
168+
func ExtractCommand(c *components.Context) []string {
169+
return slices.Clone(c.Arguments)
170+
}
171+
172+
func IsFailNoOp(context *components.Context) bool {
173+
if isContextFailNoOp(context) {
174+
return true
175+
}
176+
return isEnvFailNoOp()
177+
}
178+
179+
func isContextFailNoOp(context *components.Context) bool {
180+
if context == nil {
181+
return false
182+
}
183+
return context.GetBoolFlagValue("fail-no-op")
184+
}
185+
186+
func isEnvFailNoOp() bool {
187+
return strings.ToLower(os.Getenv(coreutils.FailNoOp)) == "true"
188+
}
189+
125190
// Get project key from flag or environment variable
126191
func GetProject(c *components.Context) string {
127192
projectKey := c.GetStringFlagValue("project")
@@ -136,6 +201,177 @@ func getOrDefaultEnv(arg, envKey string) string {
136201
return os.Getenv(envKey)
137202
}
138203

139-
func GetThreadsCount(c *components.Context) (threads int, err error) {
140-
return cliutils.GetThreadsCount(c.GetStringFlagValue("threads"))
204+
func GetBuildName(buildName string) string {
205+
return getOrDefaultEnv(buildName, coreutils.BuildName)
206+
}
207+
208+
func GetBuildUrl(buildUrl string) string {
209+
return getOrDefaultEnv(buildUrl, coreutils.BuildUrl)
210+
}
211+
212+
func GetEnvExclude(envExclude string) string {
213+
return getOrDefaultEnv(envExclude, coreutils.EnvExclude)
214+
}
215+
216+
func GetDocumentationMessage() string {
217+
return "You can read the documentation at " + coreutils.JFrogHelpUrl + "jfrog-cli"
218+
}
219+
220+
func CleanupResult(result *commandUtils.Result, err *error) {
221+
if result != nil && result.Reader() != nil {
222+
*err = errors.Join(*err, result.Reader().Close())
223+
}
224+
}
225+
226+
func PrintCommandSummary(result *commandUtils.Result, detailedSummary, printDeploymentView, failNoOp bool, originalErr error) (err error) {
227+
// We would like to print a basic summary of total failures/successes in the case of an error.
228+
err = originalErr
229+
if result == nil {
230+
// We don't have a total of failures/successes artifacts, so we are done.
231+
return
232+
}
233+
defer func() {
234+
err = GetCliError(err, result.SuccessCount(), result.FailCount(), failNoOp)
235+
}()
236+
basicSummary, err := CreateSummaryReportString(result.SuccessCount(), result.FailCount(), failNoOp, err)
237+
if err != nil {
238+
// Print the basic summary and return the original error.
239+
log.Output(basicSummary)
240+
return
241+
}
242+
if detailedSummary {
243+
err = PrintDetailedSummaryReport(basicSummary, result.Reader(), true, err)
244+
} else {
245+
if printDeploymentView {
246+
err = PrintDeploymentView(result.Reader())
247+
}
248+
log.Output(basicSummary)
249+
}
250+
return
251+
}
252+
253+
func GetCliError(err error, success, failed int, failNoOp bool) error {
254+
switch coreutils.GetExitCode(err, success, failed, failNoOp) {
255+
case coreutils.ExitCodeError:
256+
{
257+
var errorMessage string
258+
if err != nil {
259+
errorMessage = err.Error()
260+
}
261+
return coreutils.CliError{ExitCode: coreutils.ExitCodeError, ErrorMsg: errorMessage}
262+
}
263+
case coreutils.ExitCodeFailNoOp:
264+
return coreutils.CliError{ExitCode: coreutils.ExitCodeFailNoOp, ErrorMsg: "No errors, but also no files affected (fail-no-op flag)."}
265+
default:
266+
return nil
267+
}
268+
}
269+
270+
func CreateSummaryReportString(success, failed int, failNoOp bool, err error) (string, error) {
271+
summaryReport := summary.GetSummaryReport(success, failed, failNoOp, err)
272+
summaryContent, mErr := summaryReport.Marshal()
273+
if errorutils.CheckError(mErr) != nil {
274+
// Don't swallow the original error. Log the marshal error and return the original error.
275+
return "", summaryPrintError(mErr, err)
276+
}
277+
return clientutils.IndentJson(summaryContent), err
278+
}
279+
280+
// Prints a summary report.
281+
// If a resultReader is provided, we will iterate over the result and print a detailed summary including the affected files.
282+
func PrintDetailedSummaryReport(basicSummary string, reader *content.ContentReader, uploaded bool, originalErr error) error {
283+
// A reader wasn't provided, prints the basic summary json and return.
284+
if reader == nil {
285+
log.Output(basicSummary)
286+
return nil
287+
}
288+
writer, mErr := content.NewContentWriter("files", false, true)
289+
if mErr != nil {
290+
log.Output(basicSummary)
291+
return summaryPrintError(mErr, originalErr)
292+
}
293+
// We remove the closing curly bracket in order to append the affected files array using a responseWriter to write directly to stdout.
294+
basicSummary = strings.TrimSuffix(basicSummary, "\n}") + ","
295+
log.Output(basicSummary)
296+
defer log.Output("}")
297+
readerLength, _ := reader.Length()
298+
// If the reader is empty we will print an empty array.
299+
if readerLength == 0 {
300+
log.Output(" \"files\": []")
301+
} else {
302+
for transferDetails := new(clientutils.FileTransferDetails); reader.NextRecord(transferDetails) == nil; transferDetails = new(clientutils.FileTransferDetails) {
303+
writer.Write(getDetailedSummaryRecord(transferDetails, uploaded))
304+
}
305+
reader.Reset()
306+
}
307+
mErr = writer.Close()
308+
if mErr != nil {
309+
return summaryPrintError(mErr, originalErr)
310+
}
311+
rErr := reader.GetError()
312+
if rErr != nil {
313+
return summaryPrintError(rErr, originalErr)
314+
}
315+
return summaryPrintError(reader.GetError(), originalErr)
316+
}
317+
318+
// Print a file tree based on the items' path in the reader's list.
319+
func PrintDeploymentView(reader *content.ContentReader) error {
320+
tree := artifactoryUtils.NewFileTree()
321+
for transferDetails := new(clientutils.FileTransferDetails); reader.NextRecord(transferDetails) == nil; transferDetails = new(clientutils.FileTransferDetails) {
322+
tree.AddFile(transferDetails.TargetPath, "")
323+
}
324+
if err := reader.GetError(); err != nil {
325+
return err
326+
}
327+
reader.Reset()
328+
output := tree.String()
329+
if len(output) > 0 {
330+
log.Info("These files were uploaded:\n\n" + output)
331+
}
332+
return nil
333+
}
334+
335+
// Get the detailed summary record.
336+
// For uploads, we need to print the sha256 of the uploaded file along with the source and target, and prefix the target with the Artifactory URL.
337+
func getDetailedSummaryRecord(transferDetails *clientutils.FileTransferDetails, uploaded bool) interface{} {
338+
record := DetailedSummaryRecord{
339+
Source: transferDetails.SourcePath,
340+
Target: transferDetails.TargetPath,
341+
}
342+
if uploaded {
343+
record.Target = transferDetails.RtUrl + record.Target
344+
extendedRecord := ExtendedDetailedSummaryRecord{
345+
DetailedSummaryRecord: record,
346+
Sha256: transferDetails.Sha256,
347+
}
348+
return extendedRecord
349+
}
350+
record.Source = transferDetails.RtUrl + record.Source
351+
return record
352+
}
353+
354+
// Print summary report.
355+
// a given non-nil error will pass through and be returned as is if no other errors are raised.
356+
// In case of a nil error, the current function error will be returned.
357+
func summaryPrintError(summaryError, originalError error) error {
358+
if originalError != nil {
359+
if summaryError != nil {
360+
log.Error(summaryError)
361+
}
362+
return originalError
363+
}
364+
return summaryError
365+
}
366+
367+
func GetDetailedSummary(c *components.Context) bool {
368+
return c.GetBoolFlagValue("detailed-summary") || commandsummary.ShouldRecordSummary()
369+
}
370+
371+
func PrintBriefSummaryReport(success, failed int, failNoOp bool, originalErr error) error {
372+
basicSummary, mErr := CreateSummaryReportString(success, failed, failNoOp, originalErr)
373+
if mErr == nil {
374+
log.Output(basicSummary)
375+
}
376+
return summaryPrintError(mErr, originalErr)
141377
}

plugins/components/commandcomp.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package components
33
import (
44
"fmt"
55
"strconv"
6+
"strings"
67
)
78

89
type Argument struct {
@@ -54,12 +55,17 @@ type Context struct {
5455
stringFlags map[string]string
5556
boolFlags map[string]bool
5657
PrintCommandHelp func(commandName string) error
58+
ParentContext *Context
5759
}
5860

5961
func (c *Context) GetStringFlagValue(flagName string) string {
6062
return c.stringFlags[flagName]
6163
}
6264

65+
func (c *Context) SetStringFlagValue(flagName string, value string) {
66+
c.stringFlags[flagName] = value
67+
}
68+
6369
func (c *Context) AddStringFlag(key, value string) {
6470
if c.stringFlags == nil {
6571
c.stringFlags = make(map[string]string)
@@ -101,6 +107,13 @@ func (c *Context) GetBoolFlagValue(flagName string) bool {
101107
return c.boolFlags[flagName]
102108
}
103109

110+
func (c *Context) GetBoolTFlagValue(flagName string) bool {
111+
if c.IsFlagSet(flagName) {
112+
return c.boolFlags[flagName]
113+
}
114+
return true
115+
}
116+
104117
func (c *Context) IsFlagSet(flagName string) bool {
105118
if _, exist := c.stringFlags[flagName]; exist {
106119
return true
@@ -200,6 +213,12 @@ func SetMandatoryFalse() StringFlagOption {
200213
}
201214
}
202215

216+
func SetMandatoryTrue() StringFlagOption {
217+
return func(f *StringFlag) {
218+
f.Mandatory = true
219+
}
220+
}
221+
203222
func WithBoolDefaultValueFalse() BoolFlagOption {
204223
return func(f *BoolFlag) {
205224
f.DefaultValue = false
@@ -256,3 +275,25 @@ func (c *Context) WithDefaultIntFlagValue(flagName string, defValue int) (value
256275
}
257276
return
258277
}
278+
279+
func (c *Context) GetNumberOfArgs() int {
280+
return len(c.Arguments)
281+
}
282+
283+
func (c *Context) GetArgumentAt(index int) string {
284+
if len(c.Arguments) > index {
285+
return c.Arguments[index]
286+
}
287+
return ""
288+
}
289+
290+
func (c *Context) GetStringsArrFlagValue(flagName string) (resultArray []string) {
291+
if c.IsFlagSet(flagName) {
292+
resultArray = append(resultArray, strings.Split(c.GetStringFlagValue(flagName), ";")...)
293+
}
294+
return
295+
}
296+
297+
func (c *Context) GetParent() *Context {
298+
return c.ParentContext
299+
}

0 commit comments

Comments
 (0)