Skip to content

Commit

Permalink
feat: various endpoints (#114)
Browse files Browse the repository at this point in the history
* feat(configurationversion): add ConfigurationVersion

* feat(workspaces): get workspace by name / id

* chore(dependencies): downgrade axios due to "ProgressEvent" type errors

* feat(apiclient): camelcase all keys, not only root level

* chore(types): ignore linter for `any`

* feat(plans): get json plan output

* fix(runs): actions don't return any entities

* fix(run): typo

* feat(applies): add apply resource

* fix(tests): adapt to fixed typo

* fix(workspaces): typed executionMode

* feat(state-versions): add state versions
  • Loading branch information
skorfmann authored Jan 20, 2021
1 parent dde214d commit 58ef0b8
Show file tree
Hide file tree
Showing 19 changed files with 237 additions and 12 deletions.
9 changes: 9 additions & 0 deletions src/api/TerraformCloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,30 @@ import { EventEmitter } from 'events'
import terraformCloudApiClient from './terraformCloudApiClient'
import Account from './endpoints/Account'
import Plans from './endpoints/Plans'
import Applies from './endpoints/Applies'
import Runs from './endpoints/Runs'
import Workspaces from './endpoints/Workspaces'
import ConfigurationVersion from './endpoints/ConfigurationVersion'
import StateVersions from './endpoints/StateVersions'

export class TerraformCloud extends EventEmitter {
public Account: Account
public Plans: Plans
public Runs: Runs
public Applies: Applies
public Workspaces: Workspaces
public ConfigurationVersion: ConfigurationVersion
public StateVersions: StateVersions

constructor(apiKey: string) {
super()
const client = terraformCloudApiClient(apiKey)
this.Account = new Account(client)
this.Plans = new Plans(client)
this.Runs = new Runs(client)
this.Applies = new Applies(client)
this.Workspaces = new Workspaces(client)
this.ConfigurationVersion = new ConfigurationVersion(client)
this.StateVersions = new StateVersions(client)
}
}
19 changes: 19 additions & 0 deletions src/api/endpoints/Applies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { AxiosInstance } from 'axios'
import { Apply, ApplyLogs } from '../..'
import Request from './Request'

export default class Applies extends Request {
constructor(client: AxiosInstance) {
super(client)
}

async show(applyId: string): Promise<Apply> {
const path = `/applies/${applyId}`
return await this.get<Apply>(path)
}

async logs(applyId: string): Promise<ApplyLogs> {
const apply = await this.show(applyId)
return await this.client.get(apply.attributes.logReadUrl)
}
}
22 changes: 22 additions & 0 deletions src/api/endpoints/ConfigurationVersion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Request from './Request'
import { ConfigurationVersion, ConfigurationVersionRequest } from '../../types'
import axios, { AxiosInstance } from 'axios'

export default class ConfigurationVersions extends Request {
constructor(client: AxiosInstance) {
super(client)
}

create(workspaceId: string, request?: ConfigurationVersionRequest): Promise<ConfigurationVersion> {
const path = `/workspaces/${workspaceId}/configuration-versions`
return this.post<ConfigurationVersion, ConfigurationVersionRequest>(
path,
request || { data: { attributes: {}, type: 'configuration-version' } }
)
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
async upload(url: string, data: any): Promise<any> {
return await axios.put(url, data)
}
}
5 changes: 5 additions & 0 deletions src/api/endpoints/Plans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ export default class Plans extends Request {
const path = `/plans/${planId}`
return await this.get<Plan>(path)
}

async jsonOutput(planId: string): Promise<string> {
const path = `/plans/${planId}/json-output`
return await this.client.get(path)
}
}
2 changes: 1 addition & 1 deletion src/api/endpoints/Request.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AxiosInstance } from 'axios'

export default abstract class Request {
constructor(private client: AxiosInstance) {}
constructor(protected client: AxiosInstance) {}
protected async get<T>(path: string): Promise<T> {
const response = await this.client.get<T>(path)
return response.data
Expand Down
2 changes: 1 addition & 1 deletion src/api/endpoints/Runs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ export default class Runs extends Request {

async action(action: RunAction, runId: string, request?: RunActionRequest): Promise<void> {
const path = `/runs/${runId}/actions/${action}`
return await this.post<void, RunActionRequest>(path, request || {})
return await this.client.post(path, request || {})
}
}
27 changes: 27 additions & 0 deletions src/api/endpoints/StateVersions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Request from './Request'
import { StateVersion, StateVersionRequest } from '../../types'
import { AxiosInstance } from 'axios'

export default class StateVersions extends Request {
constructor(client: AxiosInstance) {
super(client)
}

create(workspaceId: string, request: StateVersionRequest): Promise<StateVersion> {
const path = `/workspaces/${workspaceId}/state-versions`
return this.post<StateVersion, StateVersionRequest>(path, request)
}

show(stateVersionId: string, includeOutputs = false): Promise<StateVersion> {
const path = `/state-versions/${stateVersionId}${includeOutputs ? '?include=outputs' : ''}`
return this.get<StateVersion>(path)
}

current(workspaceId: string, includeOutputs = false): Promise<StateVersion> {
const path = `/workspaces/${workspaceId}/current-state-version`
const params: { [key: string]: string } = {}
if (includeOutputs) params.include = 'outputs'

return this.client.get(path, { params })
}
}
10 changes: 10 additions & 0 deletions src/api/endpoints/Workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ export default class Workspaces extends Request {
super(client)
}

showByName(organizationName: string, workspaceName: string): Promise<Workspace> {
const path = `/organizations/${organizationName}/workspaces/${workspaceName}`
return this.get<Workspace>(path)
}

show(workspaceId: string): Promise<Workspace> {
const path = `/workspaces/${workspaceId}`
return this.get<Workspace>(path)
}

create(organizationName: string, request: WorkspaceRequest): Promise<Workspace> {
const path = `/organizations/${organizationName}/workspaces`
return this.post<Workspace, WorkspaceRequest>(path, request)
Expand Down
2 changes: 1 addition & 1 deletion src/api/terraformCloudApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const terraformCloudApiClient = (apiKey: string): AxiosInstance => {
return req
})

client.interceptors.response.use((res: AxiosResponse) => camelcaseKeys(res.data))
client.interceptors.response.use((res: AxiosResponse) => camelcaseKeys(res.data, { deep: true }))

return client
}
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './api/TerraformCloud'
export * from './types/Plan'
export * from './types/Apply'
export * from './types/Run'
export * from './types/TerraformCloudData'
export * from './types/User'
38 changes: 38 additions & 0 deletions src/types/Apply.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { TerraformCloudData, Data } from './TerraformCloudData'

export type Apply = TerraformCloudData<ApplyAttributes> & {
relationships: ApplyRelationship
}

export type ApplyLogs = {
firstIndex: number
lastIndex: number
size: number
data: string
}

export type StateVersionsRelationship = {
data: Data[]
}

export interface ApplyRelationship {
stateVersions: StateVersionsRelationship
}

export interface ApplyTimestamps {
queuedAt: Date
startedAt: Date
finishedAt: Date
}

export interface ApplyAttributes {
executionDetails: {
mode: 'remote'
}
status: 'pending' | 'managed_queued' | 'queued' | 'running' | 'errored' | 'canceled' | 'finished' | 'unreachable'
statusTimestamps: ApplyTimestamps
logReadUrl: string
resourceAdditions: number
resourceChanges: number
resourceDestructions: number
}
19 changes: 19 additions & 0 deletions src/types/ConfigurationVersion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Params, TerraformCloudData } from './TerraformCloudData'

export type ConfigurationVersionRequest = Params<'configuration-version', ConfigurationVersionRequestAttributes>

export interface ConfigurationVersionRequestAttributes {
autoQueueRuns?: boolean
speculative?: boolean
}
export type ConfigurationVersion = TerraformCloudData<ConfigurationVersionAttributes>

export interface ConfigurationVersionAttributes {
autoQueueRuns: boolean
error?: string
errorMessage?: string
source: string
status: string
statusTimestamps: { [key: string]: unknown }
uploadUrl: string
}
3 changes: 2 additions & 1 deletion src/types/Run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type RunActionRequest = {
export type RunRequest = {
data: {
attributes: {
iDestroy?: boolean
isDestroy?: boolean
message?: string
targetAddrs?: string[]
}
Expand All @@ -38,6 +38,7 @@ export interface RunRelationship {
confirmedBy: Relationship
createdBy: Relationship
plan: Relationship
apply: Relationship
}

export interface RunAttributes {
Expand Down
57 changes: 57 additions & 0 deletions src/types/StateVersion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Params, TerraformCloudData } from './TerraformCloudData'

export type StateVersionRequest = Params<'state-version', StateVersionRequestAttributes> & {
relationships: StateVersionRelationship
}

export interface StateVersionRelationData {
id: string
type: string
}

export interface StateVersionRelationship {
run: {
data: {
type: string
}
}
outputs: {
data: StateVersionRelationData[]
}
createdby: {
data: StateVersionRelationData[]
links: {
related: string
}
}
}

export interface StateVersionRequestAttributes {
serial: number
md5: string
lineage: string
state: string
}

export interface StateVersionIncludedOutput {
id: string
type: string
attributes: {
name: string
sensitive: boolean
type: string
value: string
}
}

export type StateVersion = TerraformCloudData<StateVersionAttributes> & {
included?: StateVersionIncludedOutput[]
}

export interface StateVersionAttributes {
vcsCommitSha?: string
vcsCommitUrl?: string
createdAt: Date
hostedStateDownloadUrl: string
serial: number
}
2 changes: 1 addition & 1 deletion src/types/Workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export interface WorkspaceAttributes {
triggerPrefixes: []
vcsRepo: null
workingDirectory: string
executionMode: string
executionMode: 'remote' | 'local' | 'agent'
}

export interface WorkspacePermissions {
Expand Down
3 changes: 3 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
export * from './User'
export * from './Plan'
export * from './Apply'
export * from './Run'
export * from './TerraformCloudData'
export * from './Workspace'
export * from './ConfigurationVersion'
export * from './StateVersion'
12 changes: 12 additions & 0 deletions test/api/Workspaces.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,16 @@ describe('Workspaces endpoints', () => {
scope.done()
done()
})

it('should camelcase keys', async done => {
const scope = nock('https://app.terraform.io/api/v2')
.patch(`/organizations/${organizationName}/workspaces/${workspaceId}`, WorkspaceNoVcsRequest)
.reply(200, WorkspaceMock)

const workspace = await Workspaces.update(organizationName, workspaceId, WorkspaceNoVcsRequest as WorkspaceRequest)

expect(workspace.attributes.createdAt).toBe('2017-11-18T00:43:59.384Z')
scope.done()
done()
})
})
2 changes: 1 addition & 1 deletion test/mocks/RunMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const RunMock = {
export const RunRequestMock: RunRequest = {
data: {
attributes: {
iDestroy: false,
isDestroy: false,
message: 'Custom message',
targetAddrs: ['example.resource_address'],
},
Expand Down
14 changes: 8 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2106,7 +2106,7 @@ axios@^0.21.1:
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies:
follow-redirects "^1.10.0"
follow-redirects "1.5.10"

babel-jest@^26.6.3:
version "26.6.3"
Expand Down Expand Up @@ -3144,7 +3144,7 @@ dateformat@^3.0.0:
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==

debug@3.1.0:
debug@3.1.0, debug@=3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
Expand Down Expand Up @@ -4016,10 +4016,12 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"

follow-redirects@^1.10.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
follow-redirects@1.5.10:
version "1.5.10"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
dependencies:
debug "=3.1.0"

for-in@^1.0.2:
version "1.0.2"
Expand Down

0 comments on commit 58ef0b8

Please sign in to comment.