github.com/bshelton229/agent@v3.5.4+incompatible/clicommand/annotate.go (about) 1 package clicommand 2 3 import ( 4 "io/ioutil" 5 "os" 6 "time" 7 8 "github.com/buildkite/agent/stdin" 9 10 "github.com/buildkite/agent/agent" 11 "github.com/buildkite/agent/api" 12 "github.com/buildkite/agent/cliconfig" 13 "github.com/buildkite/agent/logger" 14 "github.com/buildkite/agent/retry" 15 "github.com/urfave/cli" 16 ) 17 18 var AnnotateHelpDescription = `Usage: 19 20 buildkite-agent annotate [<body>] [arguments...] 21 22 Description: 23 24 Build annotations allow you to customize the Buildkite build interface to 25 show information that may surface from your builds. Some examples include: 26 27 - Links to artifacts generated by your jobs 28 - Test result summaries 29 - Graphs that include analysis about your codebase 30 - Helpful information for team members about what happened during a build 31 32 Annotations are written in CommonMark-compliant Markdown, with "GitHub 33 Flavored Markdown" extensions. 34 35 The annotation body can be supplied as a command line argument, or by piping 36 content into the command. 37 38 You can update an existing annotation's body by running the annotate command 39 again and provide the same context as the one you want to update. Or if you 40 leave context blank, it will use the default context. 41 42 You can also update just the style of an existing annotation by omitting the 43 body entirely and providing a new style value. 44 45 Example: 46 47 $ buildkite-agent annotate "All tests passed! :rocket:" 48 $ cat annotation.md | buildkite-agent annotate --style "warning" 49 $ buildkite-agent annotate --style "success" --context "junit" 50 $ ./script/dynamic_annotation_generator | buildkite-agent annotate --style "success"` 51 52 type AnnotateConfig struct { 53 Body string `cli:"arg:0" label:"annotation body"` 54 Style string `cli:"style"` 55 Context string `cli:"context"` 56 Append bool `cli:"append"` 57 Job string `cli:"job" validate:"required"` 58 AgentAccessToken string `cli:"agent-access-token" validate:"required"` 59 Endpoint string `cli:"endpoint" validate:"required"` 60 NoColor bool `cli:"no-color"` 61 Debug bool `cli:"debug"` 62 DebugHTTP bool `cli:"debug-http"` 63 } 64 65 var AnnotateCommand = cli.Command{ 66 Name: "annotate", 67 Usage: "Annotate the build page within the Buildkite UI with text from within a Buildkite job", 68 Description: AnnotateHelpDescription, 69 Flags: []cli.Flag{ 70 cli.StringFlag{ 71 Name: "context", 72 Usage: "The context of the annotation used to differentiate this annotation from others", 73 EnvVar: "BUILDKITE_ANNOTATION_CONTEXT", 74 }, 75 cli.StringFlag{ 76 Name: "style", 77 Usage: "The style of the annotation (`success`, `info`, `warning` or `error`)", 78 EnvVar: "BUILDKITE_ANNOTATION_STYLE", 79 }, 80 cli.BoolFlag{ 81 Name: "append", 82 Usage: "Append to the body of an existing annotation", 83 EnvVar: "BUILDKITE_ANNOTATION_APPEND", 84 }, 85 cli.StringFlag{ 86 Name: "job", 87 Value: "", 88 Usage: "Which job should the annotation come from", 89 EnvVar: "BUILDKITE_JOB_ID", 90 }, 91 AgentAccessTokenFlag, 92 EndpointFlag, 93 NoColorFlag, 94 DebugFlag, 95 DebugHTTPFlag, 96 }, 97 Action: func(c *cli.Context) { 98 // The configuration will be loaded into this struct 99 cfg := AnnotateConfig{} 100 101 // Load the configuration 102 loader := cliconfig.Loader{CLI: c, Config: &cfg} 103 if err := loader.Load(); err != nil { 104 logger.Fatal("%s", err) 105 } 106 107 // Setup the any global configuration options 108 HandleGlobalFlags(cfg) 109 110 var body string 111 var err error 112 113 if cfg.Body != "" { 114 body = cfg.Body 115 } else if stdin.IsReadable() { 116 logger.Info("Reading annotation body from STDIN") 117 118 // Actually read the file from STDIN 119 stdin, err := ioutil.ReadAll(os.Stdin) 120 if err != nil { 121 logger.Fatal("Failed to read from STDIN: %s", err) 122 } 123 124 body = string(stdin[:]) 125 } 126 127 // Create the API client 128 client := agent.APIClient{ 129 Endpoint: cfg.Endpoint, 130 Token: cfg.AgentAccessToken, 131 }.Create() 132 133 // Create the annotation we'll send to the Buildkite API 134 annotation := &api.Annotation{ 135 Body: body, 136 Style: cfg.Style, 137 Context: cfg.Context, 138 Append: cfg.Append, 139 } 140 141 // Retry the annotation a few times before giving up 142 err = retry.Do(func(s *retry.Stats) error { 143 // Attempt ot create the annotation 144 resp, err := client.Annotations.Create(cfg.Job, annotation) 145 146 // Don't bother retrying if the response was one of these statuses 147 if resp != nil && (resp.StatusCode == 401 || resp.StatusCode == 404 || resp.StatusCode == 400) { 148 s.Break() 149 return err 150 } 151 152 // Show the unexpected error 153 if err != nil { 154 logger.Warn("%s (%s)", err, s) 155 } 156 157 return err 158 }, &retry.Config{Maximum: 5, Interval: 1 * time.Second, Jitter: true}) 159 160 // Show a fatal error if we gave up trying to create the annotation 161 if err != nil { 162 logger.Fatal("Failed to annotate build: %s", err) 163 } 164 165 logger.Info("Successfully annotated build") 166 }, 167 }