1 | # @atomist/automation-client
|
2 |
|
3 | [![atomist sdm goals](http://badge.atomist.com/T29E48P34/atomist/automation-client-ts/bdaca35b-0cb1-4fbf-8596-a27777b46974)](https://app.atomist.com/workspace/T29E48P34)
|
4 | [![npm version](https://img.shields.io/npm/v/@atomist/automation-client.svg)](https://www.npmjs.com/package/@atomist/automation-client)
|
5 |
|
6 | [Node][node] module [`@atomist/automation-client`][automation-client]
|
7 | for creating Atomist development automations. Development automations take the following forms:
|
8 |
|
9 | - Bot commands - create bot commands using _command handlers_
|
10 | - Respond to events - use _event handlers_ to automatically
|
11 | take action when events, like someone commenting on an issue,
|
12 | happen
|
13 | - Ingestors - define your own, custom events that you can then take
|
14 | action on
|
15 |
|
16 | The automation-client provide the ability to run a client that
|
17 | connects to the Atomist API so it can receive and act on commands and
|
18 | events.
|
19 |
|
20 | See [atomist.com][atomist] and [docs.atomist.com][docs] for more
|
21 | detailed information.
|
22 |
|
23 | [node]: https://nodejs.org/ (Node.js)
|
24 | [automation-client]: https://www.npmjs.com/package/@atomist/automation-client
|
25 | [docs]: https://docs.atomist.com/ (Atomist Documentation)
|
26 |
|
27 | ## Concepts
|
28 |
|
29 | Atomist is a service and API that enables development automation. The
|
30 | Atomist service builds and maintains a model of the things that matter
|
31 | to your development team. You can then use out of the box automations
|
32 | or build your own automations acting on this model.
|
33 |
|
34 | For more information, please read [Concepts](docs/Concepts.md).
|
35 |
|
36 | ## Getting Started
|
37 |
|
38 | Please install [Node.js][node]. To verify that the right versions are
|
39 | installed, please run:
|
40 |
|
41 | ```
|
42 | $ node -v
|
43 | v8.4.0
|
44 | $ npm -v
|
45 | 5.4.1
|
46 | ```
|
47 |
|
48 | ### Using the `automation-client` module from your project
|
49 |
|
50 | To start using this module in your Node.js application, you have to add a dependency to it to your `package.json`
|
51 | by running the following command:
|
52 |
|
53 | ```
|
54 | $ npm install @atomist/automation-client --save
|
55 | ```
|
56 |
|
57 | You can find reference documentation at https://atomist.github.io/automation-client-ts/ .
|
58 |
|
59 | ### Starting from a Sample
|
60 |
|
61 | We also provide a working project with some basic automations that you can use to get started quickly. The repository
|
62 | is at [atomist/automation-seed-ts](https://github.com/atomist/automation-seed-ts).
|
63 |
|
64 | To get started run the following commands:
|
65 |
|
66 | ```
|
67 | $ git clone git@github.com:atomist/automation-seed-ts.git
|
68 | $ cd automation-seed-ts
|
69 | $ npm install
|
70 | ```
|
71 |
|
72 | See the [automation-seed-ts README][seed-readme] for further
|
73 | instructions.
|
74 |
|
75 | [seed-readme]: https://github.com/atomist/automation-seed-ts#readme
|
76 |
|
77 | ## Implementing Automations
|
78 |
|
79 | ### Command Handlers
|
80 |
|
81 | Commands are automations that can be invoked via a Chat bot, curl or web interface.
|
82 |
|
83 | To create a command take a look at the following example:
|
84 |
|
85 | ```typescript
|
86 | import { CommandHandler, Parameter} from "@atomist/automation-client/decorators";
|
87 | import { HandleCommand, HandlerContext, HandlerResult } from "@atomist/automation-client/Handlers";
|
88 |
|
89 | @CommandHandler("Sends a hello back to the client", "hello world")
|
90 | // ^ -- defines the command to trigger
|
91 | // " this handler from the bot
|
92 | export class HelloWorld implements HandleCommand {
|
93 | // ^ -- a command handler implements the HandleCommand
|
94 | // interface
|
95 |
|
96 | @Parameter({ pattern: /^.*$/, required: true })
|
97 | // ^ ^ ^ -- parameters can be marked required or optional
|
98 | // ^ ^ -- the parameter can be validated against a RegExp pattern
|
99 | // ^ -- this defines a user-provided parameter named 'name'
|
100 | public name: string;
|
101 |
|
102 |
|
103 | public handle(ctx: HandlerContext): Promise<HandlerResult> {
|
104 | // ^ ^ -- HandlerContext provides access to a 'MessageClient' for sending
|
105 | // ^ messages to the bot as well as a 'GraphClient' to query your
|
106 | // ^ data using GraphQL
|
107 | // ^ -- this method is the body of the handler and where the actual code goes
|
108 | return ctx.messageClient.respond(`Hello ${this.name}`)
|
109 | // ^ -- Calling 'respond' on the 'MessageClient' will
|
110 | // send a message back to wherever that command
|
111 | // is invoked from (eg. a DM with @atomist in Slack)
|
112 | .then(() => {
|
113 | return Promise.resolve({ code: 0 });
|
114 | // ^ -- Command handlers are expected to return a
|
115 | // 'Promise' of type 'HandlerResult' which
|
116 | // just defines a return code. Nonzero
|
117 | // return codes indicate errors.
|
118 | });
|
119 | }
|
120 | }
|
121 | ```
|
122 |
|
123 | The above declares a simple bot command that can be invoked via `@atomist hello world`. The command takes one
|
124 | parameter called `name`. The handler will respond with a simple greeting message.
|
125 |
|
126 | For more information, please see [Command Handlers](docs/CommandHandlers.md).
|
127 |
|
128 | ### Event Handlers
|
129 |
|
130 | Event handlers are automations that allow handling of events based on registered GraphQL subscriptions.
|
131 |
|
132 | To create a event handler take a look at the following example:
|
133 |
|
134 | ```typescript
|
135 | import { EventFired, EventHandler, HandleEvent, HandlerContext, HandlerResult }
|
136 | from "@atomist/automation-client/Handlers";
|
137 |
|
138 | @EventHandler("Notify channel on new issue", `subscription HelloIssue {
|
139 | Issue {
|
140 | number
|
141 | title
|
142 | repo {
|
143 | channels {
|
144 | name
|
145 | }
|
146 | }
|
147 | }
|
148 | }`)
|
149 | // ^ -- This is GraphQL subscription you want
|
150 | // to match to trigger your handler.
|
151 | // Queries can also be externalized.
|
152 | export class HelloIssue implements HandleEvent<any> {
|
153 | // ^ -- an event handler implements the 'HandleEvent'
|
154 | // interface
|
155 |
|
156 | public handle(e: EventFired<any>, ctx: HandlerContext): Promise<HandlerResult> {
|
157 | // ^ ^ -- 'HandlerContext' gives access to
|
158 | // ^ 'MessageClient' and 'GraphClient'
|
159 | // ^ -- 'EventFired' gives you access to the data that matched the
|
160 | // subscription. Since GraphQL queries return JSON it is very easy
|
161 | // to work with the data in JavaScript/TypeScript
|
162 |
|
163 | return Promise.all(e.data.Issue.map(i =>
|
164 | ctx.messageClient.addressChannels(`Got a new issue \`${i.number}# ${i.title}\``,
|
165 | // ^ -- besides responding, you can address users and
|
166 | // channels in Slack by using the respective methods
|
167 | // on 'MessageClient'
|
168 |
|
169 | i.repo.channels.map(c => c.name))))
|
170 | // ^ -- in our correlated data model, repositories can be mapped to
|
171 | // channels in a chat team. This will effectively send a message
|
172 | // into each mapped channel
|
173 | .then(() => {
|
174 | return Promise.resolve({ code: 0 });
|
175 | // ^ -- Event handlers are expected to return a
|
176 | // 'HandlerResult'. Nonzero code indicate
|
177 | // error occurred
|
178 | });
|
179 | }
|
180 | }
|
181 | ```
|
182 |
|
183 | This event handler registers a GraphQL subscription on the `Issue` type. On `Issue` events the handler will
|
184 | send a simple message back to the associated slack team.
|
185 |
|
186 | For more information, please see [Event Handlers](docs/EventHandlers.md).
|
187 |
|
188 | ### Register Handlers
|
189 |
|
190 | In order to register your handlers with the automation-client, please create a file called `atomist.config.ts` and put
|
191 | the following contents into it:
|
192 |
|
193 | ```typescript
|
194 | import { Configuration } from "@atomist/automation-client/configuration";
|
195 |
|
196 | import { HelloWorld } from "./commands/HelloWorld";
|
197 | import { HelloIssue } from "./events/HelloIssue";
|
198 |
|
199 | export const configuration: Configuration = {
|
200 | // ^ -- 'Configuration' defines all configuration options
|
201 |
|
202 | name: "your_module_name",
|
203 | // ^ -- each automation-client should have a unique name
|
204 |
|
205 | version: "0.0.1",
|
206 | // ^ -- and a semver version
|
207 |
|
208 | teamIds: ["T29E48P34"],
|
209 | // ^ -- the ids of your chat teams which you can get by running '@atomist pwd'
|
210 | // leave empty to use your user defaults saved by atomist-config
|
211 |
|
212 | commands: [
|
213 | // ^ -- register all your command handlers
|
214 | () => new HelloWorld(),
|
215 | ],
|
216 |
|
217 | events: [
|
218 | () => new HelloIssue(),
|
219 | ],
|
220 | // ^ -- the same for event handlers
|
221 |
|
222 | token: process.env.GITHUB_TOKEN || "34563sdf......................wq455eze",
|
223 | // ^ -- configure a GitHub personal access token with read:org scope; this is used to
|
224 | // authenticate the automation-client with Atomist to make sure the client should
|
225 | // be granted access to the ingested data and chat team; leave null/undefined
|
226 | // to use your user default saved by atomist-config
|
227 | };
|
228 | ```
|
229 |
|
230 | This file allows you to register your handlers as well as to specify name and version for your automation-client.
|
231 |
|
232 | ## Running the Automation-Client
|
233 |
|
234 | There are several ways you can run your automation-client and have it connect to Atomist API.
|
235 |
|
236 | ### Running Locally
|
237 |
|
238 | To start up the client locally and have it listen to incoming events, just run:
|
239 |
|
240 | ```
|
241 | $ atomist start
|
242 | ```
|
243 |
|
244 | ### Pushing to Cloud Foundry
|
245 |
|
246 | To prepare for your automation-client to run on any Cloud Foundry
|
247 | instance, please make sure that you have an account on an instance of
|
248 | Cloud Foundry and that you have the Cloud Foundry CLI installed,
|
249 | configured and logged in.
|
250 |
|
251 | First you need to create a `manifest.yml` in the root of your node
|
252 | project. Put the following minimum content into the file:
|
253 |
|
254 | ```
|
255 |
|
256 | applications:
|
257 | - name: YOUR_APP_NAME
|
258 | command: $(npm bin)/atomist-client
|
259 | memory: 128M
|
260 | buildpack: https://github.com/cloudfoundry/nodejs-buildpack
|
261 | env:
|
262 | GITHUB_TOKEN: <your GITHUB_TOKEN>
|
263 | SUPPRESS_NO_CONFIG_WARNING: true
|
264 | ```
|
265 |
|
266 | There more recommended ways for getting your `GITHUB_TOKEN` into your automation-client instance.
|
267 | Take a look at [`cfenv`](https://www.npmjs.com/package/cfenv) for example
|
268 |
|
269 | Next please add an `"engines"` top-level entry to your `package.json`
|
270 | like the following:
|
271 |
|
272 | ```javascript
|
273 | "engines": {
|
274 | "node": "8.x.x",
|
275 | "npm": "5.x.x"
|
276 | }
|
277 | ```
|
278 |
|
279 | Now you're ready to `cf push` your automation server to Cloud Foundry:
|
280 |
|
281 | ```
|
282 | $ cf push
|
283 | ```
|
284 |
|
285 | ## REST APIs
|
286 |
|
287 | When starting up, the `automation-client` exposes a couple of endpoints that can be accessed via HTTP.
|
288 |
|
289 | ### Authentication
|
290 |
|
291 | The endpoints are protected by HTTP Basic Auth or token-based authentication. When starting the client, you'll see
|
292 | a log message of the following format:
|
293 |
|
294 | ```
|
295 | 2017-09-20T08:22:32.789Z - info : Auto-generated credentials for web endpoints are user 'admin' and password '4d6390d1-de5c-6764-a078-7308503ddba5'
|
296 | ```
|
297 |
|
298 | By default the automation-client auto-generates some credentials for you use. To configure your own credentials, change
|
299 | `atomist.config.ts` and put a following section in:
|
300 |
|
301 | ```typescript
|
302 | export const configuration: Configuration = {
|
303 | ...
|
304 | http: {
|
305 | enabled: true,
|
306 | auth: {
|
307 | basic: {
|
308 | enabled: true,
|
309 | username: "some user",
|
310 | password: "some password",
|
311 | },
|
312 | },
|
313 | },
|
314 | };
|
315 | ```
|
316 |
|
317 | ### Endpoints
|
318 |
|
319 | #### GET Management Endpoints
|
320 |
|
321 | | Path | Description |
|
322 | |-------|-------------|
|
323 | | `/metrics` | exposes metrics around command, event handler executions |
|
324 | | `/health` | endpoint that exposes health information of the automation client |
|
325 | | `/registration` | metadata of all available automations |
|
326 | | `/info` | exposes information about this automation client |
|
327 | | `/log/commands` | all incoming request for running command handlers |
|
328 | | `/log/events` | all incoming events for event handlers |
|
329 | | `/log/messages` | all outgoing messages sent by handlers |
|
330 |
|
331 | As an example, here is an a command to get the current metrics:
|
332 |
|
333 | ```
|
334 | $ curl -X GET \
|
335 | http://localhost:2866/metrics \
|
336 | -H 'authorization: Bearer 34563sdf......................wq455eze"' \
|
337 | -H 'content-type: application/json'
|
338 | ```
|
339 |
|
340 | The above endpoints are all HTTP GET and take bearer and basic auth per default. See below for more details about
|
341 | authentication.
|
342 |
|
343 | #### Invoking a command handler
|
344 |
|
345 | Command handlers can be invoked via a HTTP POST with the following payload:
|
346 |
|
347 | ```
|
348 | $ curl -X POST \
|
349 | http://localhost:2866/command \
|
350 | -H 'content-type: application/json' \
|
351 | -H 'authorization: Bearer 34563sdf......................wq455eze"'
|
352 | -d '{
|
353 | "parameters": [{
|
354 | "name": "name",
|
355 | "value": "cd"
|
356 | }],
|
357 | "mapped_parameters": [{
|
358 | "name": "sender",
|
359 | "value": "kipz"
|
360 | },{
|
361 | "name": "userName",
|
362 | "value": "cd"
|
363 | }],
|
364 | "secrets": [{
|
365 | "uri": "github://user_token?scopes=repo",
|
366 | "value":"4...d"
|
367 | }],
|
368 | "command": "HelloWorld",
|
369 | "correlation_id": "6cb72bf7-77b4-4939-b892-00000701fa53",
|
370 | "team": {
|
371 | "id": "T1L0VDKJP",
|
372 | "name": "Atomist QA"
|
373 | },
|
374 | "api_version": "1"
|
375 | }'
|
376 | ```
|
377 |
|
378 | ## Support
|
379 |
|
380 | General support questions should be discussed in the `#support`
|
381 | channel on our community Slack team
|
382 | at [atomist-community.slack.com][slack].
|
383 |
|
384 | If you find a problem, please create an [issue][].
|
385 |
|
386 | [issue]: https://github.com/atomist/automation-client-ts/issues
|
387 |
|
388 | ## Development
|
389 |
|
390 | You will need to install [node][] to build and test this project.
|
391 |
|
392 | To run tests, define a GITHUB_TOKEN to any valid token that has repo access. The tests
|
393 | will create and delete repositories.
|
394 |
|
395 | Define GITHUB_VISIBILITY=public if you want these to be public; default is private.
|
396 | You'll get a 422 response from repo creation if you don't pay for private repos.
|
397 |
|
398 | ### Build and Test
|
399 |
|
400 | Command | Reason
|
401 | ------- | ------
|
402 | `npm install` | install all the required packages
|
403 | `npm run build` | lint, compile, and test
|
404 | `npm start` | start the Atomist automation client
|
405 | `npm run autostart` | run the client, refreshing when files change
|
406 | `npm run lint` | run tslint against the TypeScript
|
407 | `npm run compile` | compile all TypeScript into JavaScript
|
408 | `npm test` | run tests and ensure everything is working
|
409 | `npm run autotest` | run tests continuously
|
410 | `npm run clean` | remove stray compiled JavaScript files and build directory
|
411 |
|
412 | ### Release
|
413 |
|
414 | To create a new release of the project, we push a button on the Atomist lifecycle message
|
415 | in the #automation-client-ts [channel](https://atomist-community.slack.com/messages/C74J6MFL0/) in Atomist Community Slack.
|
416 |
|
417 | ---
|
418 |
|
419 | Created by [Atomist][atomist].
|
420 | Need Help? [Join our Slack team][slack].
|
421 |
|
422 | [atomist]: https://atomist.com/ (Atomist - Development Automation)
|
423 | [slack]: https://join.atomist.com/ (Atomist Community Slack)
|