github.com/jenkins-x/jx/v2@v2.1.155/pkg/cmd/get/get_activity.go (about) 1 package get 2 3 import ( 4 "strings" 5 "time" 6 7 "github.com/jenkins-x/jx/v2/pkg/cmd/helper" 8 9 "github.com/ghodss/yaml" 10 v1 "github.com/jenkins-x/jx-api/pkg/apis/jenkins.io/v1" 11 "github.com/jenkins-x/jx-api/pkg/client/clientset/versioned" 12 "github.com/jenkins-x/jx-logging/pkg/log" 13 "github.com/jenkins-x/jx/v2/pkg/cmd/opts" 14 "github.com/jenkins-x/jx/v2/pkg/cmd/templates" 15 "github.com/jenkins-x/jx/v2/pkg/kube" 16 tbl "github.com/jenkins-x/jx/v2/pkg/table" 17 "github.com/jenkins-x/jx/v2/pkg/util" 18 "github.com/spf13/cobra" 19 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 "k8s.io/apimachinery/pkg/fields" 21 "k8s.io/client-go/tools/cache" 22 ) 23 24 const ( 25 indentation = " " 26 ) 27 28 // GetActivityOptions containers the CLI options 29 type GetActivityOptions struct { 30 *opts.CommonOptions 31 32 Filter string 33 BuildNumber string 34 Watch bool 35 Sort bool 36 } 37 38 var ( 39 get_activity_long = templates.LongDesc(` 40 Display the current activities for one or more projects. 41 `) 42 43 get_activity_example = templates.Examples(` 44 # List the current activities for all applications in the current team 45 jx get activities 46 47 # List the current activities for application 'foo' 48 jx get act -f foo 49 50 # Watch the activities for application 'foo' 51 jx get act -f foo -w 52 `) 53 ) 54 55 // NewCmdGetActivity creates the new command for: jx get version 56 func NewCmdGetActivity(commonOpts *opts.CommonOptions) *cobra.Command { 57 options := &GetActivityOptions{ 58 CommonOptions: commonOpts, 59 } 60 cmd := &cobra.Command{ 61 Use: "activities", 62 Short: "Display one or more Activities on projects", 63 Aliases: []string{"activity", "act"}, 64 Long: get_activity_long, 65 Example: get_activity_example, 66 Run: func(cmd *cobra.Command, args []string) { 67 options.Cmd = cmd 68 options.Args = args 69 err := options.Run() 70 helper.CheckErr(err) 71 }, 72 } 73 cmd.Flags().StringVarP(&options.Filter, "filter", "f", "", "Text to filter the pipeline names") 74 cmd.Flags().StringVarP(&options.BuildNumber, "build", "", "", "The build number to filter on") 75 cmd.Flags().BoolVarP(&options.Watch, "watch", "w", false, "Whether to watch the activities for changes") 76 cmd.Flags().BoolVarP(&options.Sort, "sort", "s", false, "Sort activities by timestamp") 77 return cmd 78 } 79 80 // Run implements this command 81 func (o *GetActivityOptions) Run() error { 82 client, currentNs, err := o.JXClientAndDevNamespace() 83 if err != nil { 84 return err 85 } 86 kubeClient, err := o.KubeClient() 87 if err != nil { 88 return err 89 } 90 ns, _, err := kube.GetDevNamespace(kubeClient, currentNs) 91 if err != nil { 92 return err 93 } 94 table := o.CreateTable() 95 table.SetColumnAlign(1, util.ALIGN_RIGHT) 96 table.SetColumnAlign(2, util.ALIGN_RIGHT) 97 table.AddRow("STEP", "STARTED AGO", "DURATION", "STATUS") 98 99 if o.Watch { 100 return o.WatchActivities(&table, client, ns) 101 } 102 103 list, err := client.JenkinsV1().PipelineActivities(ns).List(metav1.ListOptions{}) 104 if err != nil { 105 return err 106 } 107 if o.Sort { 108 kube.SortActivities(list.Items) 109 } 110 111 for _, activity := range list.Items { 112 a := activity 113 o.addTableRow(&table, &a) 114 } 115 table.Render() 116 117 return nil 118 } 119 120 func (o *GetActivityOptions) addTableRow(table *tbl.Table, activity *v1.PipelineActivity) bool { 121 if o.matches(activity) { 122 spec := &activity.Spec 123 text := "" 124 version := activity.Spec.Version 125 if version != "" { 126 text = "Version: " + util.ColorInfo(version) 127 } 128 statusText := statusString(activity.Spec.Status) 129 if statusText == "" { 130 statusText = text 131 } else { 132 statusText += " " + text 133 } 134 table.AddRow(spec.Pipeline+" #"+spec.Build, 135 timeToString(spec.StartedTimestamp), 136 util.DurationString(spec.StartedTimestamp, spec.CompletedTimestamp), 137 statusText) 138 indent := indentation 139 for _, step := range spec.Steps { 140 s := step 141 o.addStepRow(table, &s, indent) 142 } 143 return true 144 } 145 return false 146 } 147 148 func (o *GetActivityOptions) WatchActivities(table *tbl.Table, jxClient versioned.Interface, ns string) error { 149 yamlSpecMap := map[string]string{} 150 activity := &v1.PipelineActivity{} 151 listWatch := cache.NewListWatchFromClient(jxClient.JenkinsV1().RESTClient(), "pipelineactivities", ns, fields.Everything()) 152 kube.SortListWatchByName(listWatch) 153 _, controller := cache.NewInformer( 154 listWatch, 155 activity, 156 time.Minute*10, 157 cache.ResourceEventHandlerFuncs{ 158 AddFunc: func(obj interface{}) { 159 o.onActivity(table, obj, yamlSpecMap) 160 }, 161 UpdateFunc: func(oldObj, newObj interface{}) { 162 o.onActivity(table, newObj, yamlSpecMap) 163 }, 164 DeleteFunc: func(obj interface{}) { 165 }, 166 }, 167 ) 168 169 stop := make(chan struct{}) 170 go controller.Run(stop) 171 172 // Wait forever 173 select {} 174 } 175 176 func (o *GetActivityOptions) onActivity(table *tbl.Table, obj interface{}, yamlSpecMap map[string]string) { 177 activity, ok := obj.(*v1.PipelineActivity) 178 if !ok { 179 log.Logger().Infof("Object is not a PipelineActivity %#v", obj) 180 return 181 } 182 data, err := yaml.Marshal(&activity.Spec) 183 if err != nil { 184 log.Logger().Infof("Failed to marshal Activity.Spec to YAML: %s", err) 185 } else { 186 text := string(data) 187 name := activity.Name 188 old := yamlSpecMap[name] 189 if old == "" || old != text { 190 yamlSpecMap[name] = text 191 if o.addTableRow(table, activity) { 192 table.Render() 193 table.Clear() 194 } 195 } 196 } 197 } 198 199 func (o *GetActivityOptions) addStepRow(table *tbl.Table, parent *v1.PipelineActivityStep, indent string) { 200 stage := parent.Stage 201 preview := parent.Preview 202 promote := parent.Promote 203 if stage != nil { 204 addStageRow(table, stage, indent) 205 } else if preview != nil { 206 addPreviewRow(table, preview, indent) 207 } else if promote != nil { 208 addPromoteRow(table, promote, indent) 209 } else { 210 log.Logger().Warnf("Unknown step kind %#v", parent) 211 } 212 } 213 214 func addStageRow(table *tbl.Table, stage *v1.StageActivityStep, indent string) { 215 name := "Stage" 216 if stage.Name != "" { 217 name = "" 218 } 219 addStepRowItem(table, &stage.CoreActivityStep, indent, name, "") 220 221 indent += indentation 222 for _, step := range stage.Steps { 223 s := step 224 addStepRowItem(table, &s, indent, "", "") 225 } 226 } 227 228 func addPreviewRow(table *tbl.Table, parent *v1.PreviewActivityStep, indent string) { 229 pullRequestURL := parent.PullRequestURL 230 if pullRequestURL == "" { 231 pullRequestURL = parent.Environment 232 } 233 addStepRowItem(table, &parent.CoreActivityStep, indent, "Preview", util.ColorInfo(pullRequestURL)) 234 indent += indentation 235 236 appURL := parent.ApplicationURL 237 if appURL != "" { 238 addStepRowItem(table, &parent.CoreActivityStep, indent, "Preview Application", util.ColorInfo(appURL)) 239 } 240 } 241 242 func addPromoteRow(table *tbl.Table, parent *v1.PromoteActivityStep, indent string) { 243 addStepRowItem(table, &parent.CoreActivityStep, indent, "Promote: "+parent.Environment, "") 244 indent += indentation 245 246 pullRequest := parent.PullRequest 247 update := parent.Update 248 if pullRequest != nil { 249 addStepRowItem(table, &pullRequest.CoreActivityStep, indent, "PullRequest", describePromotePullRequest(pullRequest)) 250 } 251 if update != nil { 252 addStepRowItem(table, &update.CoreActivityStep, indent, "Update", describePromoteUpdate(update)) 253 } 254 appURL := parent.ApplicationURL 255 if appURL != "" { 256 addStepRowItem(table, &update.CoreActivityStep, indent, "Promoted", " Application is at: "+util.ColorInfo(appURL)) 257 } 258 } 259 260 func addStepRowItem(table *tbl.Table, step *v1.CoreActivityStep, indent string, name string, description string) { 261 text := step.Description 262 if description != "" { 263 if text == "" { 264 text = description 265 } else { 266 text += " " + description 267 } 268 } 269 textName := step.Name 270 if textName == "" { 271 textName = name 272 } else { 273 if name != "" { 274 textName = name + ":" + textName 275 } 276 } 277 table.AddRow(indent+textName, 278 timeToString(step.StartedTimestamp), 279 util.DurationString(step.StartedTimestamp, step.CompletedTimestamp), 280 statusString(step.Status)+" "+text) 281 } 282 283 func statusString(statusType v1.ActivityStatusType) string { 284 text := statusType.String() 285 switch statusType { 286 case v1.ActivityStatusTypeFailed, v1.ActivityStatusTypeError: 287 return util.ColorError(text) 288 case v1.ActivityStatusTypeSucceeded: 289 return util.ColorInfo(text) 290 case v1.ActivityStatusTypeRunning: 291 return util.ColorStatus(text) 292 } 293 return text 294 } 295 296 func describePromotePullRequest(promote *v1.PromotePullRequestStep) string { 297 description := "" 298 if promote.PullRequestURL != "" { 299 description += " PullRequest: " + util.ColorInfo(promote.PullRequestURL) 300 } 301 if promote.MergeCommitSHA != "" { 302 description += " Merge SHA: " + util.ColorInfo(promote.MergeCommitSHA) 303 } 304 return description 305 } 306 307 func describePromoteUpdate(promote *v1.PromoteUpdateStep) string { 308 description := "" 309 for _, status := range promote.Statuses { 310 url := status.URL 311 state := status.Status 312 313 if url != "" && state != "" { 314 description += " Status: " + pullRequestStatusString(state) + " at: " + util.ColorInfo(url) 315 } 316 } 317 return description 318 } 319 320 func pullRequestStatusString(text string) string { 321 title := strings.Title(text) 322 switch text { 323 case "success": 324 return util.ColorInfo(title) 325 case "error", "failed": 326 return util.ColorError(title) 327 default: 328 return util.ColorStatus(title) 329 } 330 } 331 332 func timeToString(t *metav1.Time) string { 333 if t == nil { 334 return "" 335 } 336 now := &metav1.Time{ 337 Time: time.Now(), 338 } 339 return util.DurationString(t, now) 340 } 341 342 func (o *GetActivityOptions) matches(activity *v1.PipelineActivity) bool { 343 answer := true 344 filter := o.Filter 345 if filter != "" { 346 answer = strings.Contains(activity.Name, filter) || strings.Contains(activity.Spec.Pipeline, filter) 347 } 348 build := o.BuildNumber 349 if answer && build != "" { 350 answer = activity.Spec.Build == build 351 } 352 return answer 353 }