11package common
22
33import (
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+
1736func 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+
6083func 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
126191func 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}
0 commit comments