| 
									
										
										
										
											2018-05-10 13:56:07 +01:00
										 |  |  | // Imports
 | 
					
						
							|  |  |  | import fetch from 'node-fetch'; | 
					
						
							|  |  |  | import {assertNotMissingOrEmpty} from './utils'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Constants
 | 
					
						
							|  |  |  | const CIRCLE_CI_API_URL = 'https://circleci.com/api/v1.1/project/github'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Interfaces - Types
 | 
					
						
							|  |  |  | export interface ArtifactInfo { | 
					
						
							|  |  |  |   path: string; | 
					
						
							|  |  |  |   pretty_path: string; | 
					
						
							|  |  |  |   node_index: number; | 
					
						
							|  |  |  |   url: string; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export type ArtifactResponse = ArtifactInfo[]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export interface BuildInfo { | 
					
						
							|  |  |  |   reponame: string; | 
					
						
							|  |  |  |   failed: boolean; | 
					
						
							|  |  |  |   branch: string; | 
					
						
							|  |  |  |   username: string; | 
					
						
							|  |  |  |   build_num: number; | 
					
						
							|  |  |  |   has_artifacts: boolean; | 
					
						
							|  |  |  |   outcome: string; // e.g. 'success'
 | 
					
						
							|  |  |  |   vcs_revision: string; // HEAD SHA
 | 
					
						
							|  |  |  |   // there are other fields but they are not used in this code
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * A Helper that can interact with the CircleCI API. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export class CircleCiApi { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private tokenParam = `circle-token=${this.circleCiToken}`; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Construct a helper that can interact with the CircleCI REST API. | 
					
						
							|  |  |  |    * @param githubOrg The Github organisation whose repos we want to access in CircleCI (e.g. angular). | 
					
						
							|  |  |  |    * @param githubRepo The Github repo whose builds we want to access in CircleCI (e.g. angular). | 
					
						
							|  |  |  |    * @param circleCiToken The CircleCI API access token (secret). | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   constructor( | 
					
						
							|  |  |  |     private githubOrg: string, | 
					
						
							|  |  |  |     private githubRepo: string, | 
					
						
							|  |  |  |     private circleCiToken: string, | 
					
						
							|  |  |  |   ) { | 
					
						
							|  |  |  |     assertNotMissingOrEmpty('githubOrg', githubOrg); | 
					
						
							|  |  |  |     assertNotMissingOrEmpty('githubRepo', githubRepo); | 
					
						
							|  |  |  |     assertNotMissingOrEmpty('circleCiToken', circleCiToken); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Get the info for a build from the CircleCI API | 
					
						
							|  |  |  |    * @param buildNumber The CircleCI build number that generated the artifact. | 
					
						
							|  |  |  |    * @returns A promise to the info about the build | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2018-08-10 11:10:22 +01:00
										 |  |  |   public async getBuildInfo(buildNumber: number): Promise<BuildInfo> { | 
					
						
							| 
									
										
										
										
											2018-05-10 13:56:07 +01:00
										 |  |  |     try { | 
					
						
							|  |  |  |       const baseUrl = `${CIRCLE_CI_API_URL}/${this.githubOrg}/${this.githubRepo}/${buildNumber}`; | 
					
						
							|  |  |  |       const response = await fetch(`${baseUrl}?${this.tokenParam}`); | 
					
						
							|  |  |  |       if (response.status !== 200) { | 
					
						
							|  |  |  |         throw new Error(`${baseUrl}: ${response.status} - ${response.statusText}`); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2018-08-27 13:31:38 +03:00
										 |  |  |       return response.json(); | 
					
						
							| 
									
										
										
										
											2018-05-10 13:56:07 +01:00
										 |  |  |     } catch (error) { | 
					
						
							|  |  |  |         throw new Error(`CircleCI build info request failed (${error.message})`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Query the CircleCI API to get a URL for a specified artifact from a specified build. | 
					
						
							|  |  |  |    * @param artifactPath The path, within the build to the artifact. | 
					
						
							|  |  |  |    * @returns A promise to the URL that can be requested to download the actual build artifact file. | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2018-08-10 11:10:22 +01:00
										 |  |  |   public async getBuildArtifactUrl(buildNumber: number, artifactPath: string): Promise<string> { | 
					
						
							| 
									
										
										
										
											2018-05-10 13:56:07 +01:00
										 |  |  |     const baseUrl = `${CIRCLE_CI_API_URL}/${this.githubOrg}/${this.githubRepo}/${buildNumber}`; | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       const response = await fetch(`${baseUrl}/artifacts?${this.tokenParam}`); | 
					
						
							| 
									
										
										
										
											2018-08-27 13:31:38 +03:00
										 |  |  |       const artifacts = await response.json() as ArtifactResponse; | 
					
						
							| 
									
										
										
										
											2018-05-10 13:56:07 +01:00
										 |  |  |       const artifact = artifacts.find(item => item.path === artifactPath); | 
					
						
							|  |  |  |       if (!artifact) { | 
					
						
							|  |  |  |         throw new Error(`Missing artifact (${artifactPath}) for CircleCI build: ${buildNumber}`); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return artifact.url; | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       throw new Error(`CircleCI artifact URL request failed (${error.message})`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |