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