github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/cmd/get/get_activity.go (about)

     1  package get
     2  
     3  import (
     4  	"strings"
     5  	"time"
     6  
     7  	"github.com/olli-ai/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/olli-ai/jx/v2/pkg/cmd/opts"
    14  	"github.com/olli-ai/jx/v2/pkg/cmd/templates"
    15  	"github.com/olli-ai/jx/v2/pkg/kube"
    16  	tbl "github.com/olli-ai/jx/v2/pkg/table"
    17  	"github.com/olli-ai/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  }