github.com/jfrog/jfrog-cli-core/v2@v2.52.0/pipelines/commands/status.go (about) 1 package commands 2 3 import ( 4 "context" 5 "fmt" 6 "github.com/gookit/color" 7 "github.com/jfrog/jfrog-cli-core/v2/pipelines/manager" 8 "github.com/jfrog/jfrog-cli-core/v2/pipelines/status" 9 "github.com/jfrog/jfrog-cli-core/v2/utils/config" 10 "github.com/jfrog/jfrog-client-go/pipelines" 11 "github.com/jfrog/jfrog-client-go/pipelines/services" 12 "github.com/jfrog/jfrog-client-go/utils" 13 "github.com/jfrog/jfrog-client-go/utils/log" 14 "golang.org/x/exp/slices" 15 "time" 16 ) 17 18 type StatusCommand struct { 19 // Server details with pipelines server URl and authentication 20 serverDetails *config.ServerDetails 21 // Branch name for applying filter on pipeline statuses 22 branch string 23 // Pipeline name to apply filter on pipeline statuses 24 pipelineName string 25 // Notify used for determining for continuous monitoring of status and notifying 26 notify bool 27 isMultiBranch bool 28 } 29 30 const ( 31 PipelineName = "PipelineName :" 32 Branch = "Branch :" 33 Run = "Run :" 34 Duration = "Duration :" 35 StatusLabel = "Status :" 36 MaxRetries = 1500 37 MinimumIntervalRetriesInMilliSecs = 5000 38 ) 39 40 func NewStatusCommand() *StatusCommand { 41 return &StatusCommand{} 42 } 43 44 func (sc *StatusCommand) ServerDetails() (*config.ServerDetails, error) { 45 return sc.serverDetails, nil 46 } 47 48 func (sc *StatusCommand) SetServerDetails(serverDetails *config.ServerDetails) *StatusCommand { 49 sc.serverDetails = serverDetails 50 return sc 51 } 52 53 func (sc *StatusCommand) CommandName() string { 54 return "pl_status" 55 } 56 57 func (sc *StatusCommand) SetBranch(br string) *StatusCommand { 58 sc.branch = br 59 return sc 60 } 61 62 func (sc *StatusCommand) SetPipeline(pl string) *StatusCommand { 63 sc.pipelineName = pl 64 return sc 65 } 66 67 func (sc *StatusCommand) SetNotify(nf bool) *StatusCommand { 68 sc.notify = nf 69 return sc 70 } 71 72 func (sc *StatusCommand) SetMultiBranch(multiBranch bool) *StatusCommand { 73 sc.isMultiBranch = multiBranch 74 return sc 75 } 76 77 func (sc *StatusCommand) Run() error { 78 // Create service manager to fetch run status 79 serviceManager, err := manager.CreateServiceManager(sc.serverDetails) 80 if err != nil { 81 return err 82 } 83 84 // Get pipeline status using branch name, pipelines name and whether it is multi-branch 85 matchingPipes, err := serviceManager.GetPipelineRunStatusByBranch(sc.branch, sc.pipelineName, sc.isMultiBranch) 86 if err != nil { 87 return err 88 } 89 var res string 90 for i := range matchingPipes.Pipelines { 91 pipe := matchingPipes.Pipelines[i] 92 // Eliminate pipelines which have not been run 93 if pipe.LatestRunID != 0 { 94 // When notification option is selected use this flow to notify 95 if sc.pipelineName != "" && sc.notify { 96 err := monitorStatusAndNotify(context.Background(), serviceManager, sc.branch, sc.pipelineName, sc.isMultiBranch) 97 if err != nil { 98 return err 99 } 100 } else { 101 respStatus, colorCode, duration := getPipelineStatusAndColorCode(&pipe) 102 res += colorCode.Sprintf("\n%s %s\n%14s %s\n%14s %d \n%14s %s \n%14s %s\n", PipelineName, 103 pipe.Name, Branch, pipe.PipelineSourceBranch, Run, pipe.Run.RunNumber, Duration, 104 duration, StatusLabel, string(respStatus)) 105 } 106 } 107 } 108 log.Output(res) 109 return nil 110 } 111 112 // getPipelineStatusAndColorCode from received pipeline statusCode 113 // return PipelineStatus - pipeline status string conversion from statusCode 114 // colorCode - color to be used for text formatting 115 // duration - duration for the pipeline in seconds 116 func getPipelineStatusAndColorCode(pipeline *services.Pipelines) (pipelineStatus status.PipelineStatus, colorCode color.Color, duration string) { 117 pipelineStatus = status.GetPipelineStatus(pipeline.Run.StatusCode) 118 colorCode = status.GetStatusColorCode(pipelineStatus) 119 durationSeconds := pipeline.Run.DurationSeconds 120 if durationSeconds == 0 { 121 // Calculate the duration time by differentiating created time from the current time 122 durationSeconds = int(time.Now().Unix() - pipeline.Run.CreatedAt.Unix()) 123 } 124 125 return pipelineStatus, colorCode, convertSecToDay(durationSeconds) 126 } 127 128 // ConvertSecToDay converts seconds passed as integer to Days, Hours, Minutes, Seconds 129 // Duration in D H M S format for example 124 seconds to "0D 0H 2M 4S" 130 func convertSecToDay(sec int) string { 131 log.Debug("Duration time in seconds:", sec) 132 day := sec / (24 * 3600) 133 134 sec %= 24 * 3600 135 hour := sec / 3600 136 137 sec %= 3600 138 minutes := sec / 60 139 140 sec %= 60 141 seconds := sec 142 143 return fmt.Sprintf("%dD %dH %dM %dS", day, hour, minutes, seconds) 144 } 145 146 // monitorStatusAndNotify monitors for status change and 147 // sends notification if there is a change identified in the pipeline run status 148 func monitorStatusAndNotify(ctx context.Context, pipelinesMgr *pipelines.PipelinesServicesManager, branch string, pipName string, isMultiBranch bool) error { 149 var previousStatus string 150 151 retryExecutor := utils.RetryExecutor{ 152 Context: ctx, 153 MaxRetries: MaxRetries, 154 RetriesIntervalMilliSecs: MinimumIntervalRetriesInMilliSecs, 155 ExecutionHandler: func() (shouldRetry bool, err error) { 156 pipelineStatus, err := pipelinesMgr.GetPipelineRunStatusByBranch(branch, pipName, isMultiBranch) 157 if err != nil { 158 // Pipelines is expected to be available. Any error is not expected and no need to retry. 159 return false, err 160 } 161 pipeline := pipelineStatus.Pipelines[0] 162 currentStatus, colorCode, duration := getPipelineStatusAndColorCode(&pipeline) 163 if pipelineStatusChanged(string(currentStatus), previousStatus) { 164 changedPipelineStatus := colorCode.Sprintf("\n%s %s\n%14s %s\n%14s %d \n%14s %s \n%14s %s\n", PipelineName, 165 pipeline.Name, Branch, pipeline.PipelineSourceBranch, Run, pipeline.Run.RunNumber, Duration, 166 duration, StatusLabel, string(currentStatus)) 167 log.Output(changedPipelineStatus) 168 if pipelineRunEnded(string(currentStatus)) { 169 return false, nil 170 } 171 } 172 previousStatus = string(currentStatus) 173 // Should retry even though successful since retry mechanism is trying to fetch pipeline status continuously 174 return true, nil 175 }, 176 } 177 178 return retryExecutor.Execute() 179 } 180 181 // pipelineStatusChanged returns true if the current pipeline status is different from the previous one. 182 // Return false otherwise. 183 func pipelineStatusChanged(currentStatus, previousState string) bool { 184 log.Debug("Previous status: %s current status: %s", previousState, currentStatus) 185 return previousState != currentStatus 186 } 187 188 // pipelineRunEnded if pipeline status is one of 189 // CANCELLED, FAILED, SUCCESS, ERROR, TIMEOUT pipeline run 190 // life is considered to be done. 191 func pipelineRunEnded(pipStatus string) bool { 192 pipRunEndLife := []string{string(status.SUCCESS), string(status.FAILURE), string(status.ERROR), string(status.CANCELLED), string(status.TIMEOUT)} 193 return slices.Contains(pipRunEndLife, pipStatus) 194 }