@@ -29,8 +29,10 @@ export const runContainer = async (
29
29
return containerID . trim ( ) ;
30
30
} ;
31
31
32
- // executeScriptInContainer finds the only "coder_script"
33
- // resource in the given state and runs it in a container.
32
+ /**
33
+ * Finds the only "coder_script" resource in the given state and runs it in a
34
+ * container.
35
+ */
34
36
export const executeScriptInContainer = async (
35
37
state : TerraformState ,
36
38
image : string ,
@@ -76,27 +78,30 @@ export const execContainer = async (
76
78
} ;
77
79
} ;
78
80
81
+ type JsonValue =
82
+ | string
83
+ | number
84
+ | boolean
85
+ | null
86
+ | JsonValue [ ]
87
+ | { [ key : string ] : JsonValue } ;
88
+
89
+ type TerraformStateResource = {
90
+ type : string ;
91
+ name : string ;
92
+ provider : string ;
93
+ instances : [ { attributes : Record < string , any > } ] ;
94
+ } ;
95
+
79
96
export interface TerraformState {
80
97
outputs : {
81
98
[ key : string ] : {
82
99
type : string ;
83
100
value : any ;
84
101
} ;
85
- }
86
- resources : [
87
- {
88
- type : string ;
89
- name : string ;
90
- provider : string ;
91
- instances : [
92
- {
93
- attributes : {
94
- [ key : string ] : any ;
95
- } ;
96
- } ,
97
- ] ;
98
- } ,
99
- ] ;
102
+ } ;
103
+
104
+ resources : [ TerraformStateResource , ...TerraformStateResource [ ] ] ;
100
105
}
101
106
102
107
export interface CoderScriptAttributes {
@@ -105,10 +110,11 @@ export interface CoderScriptAttributes {
105
110
url : string ;
106
111
}
107
112
108
- // findResourceInstance finds the first instance of the given resource
109
- // type in the given state. If name is specified, it will only find
110
- // the instance with the given name.
111
- export const findResourceInstance = < T extends "coder_script" | string > (
113
+ /**
114
+ * finds the first instance of the given resource type in the given state. If
115
+ * name is specified, it will only find the instance with the given name.
116
+ */
117
+ export const findResourceInstance = < T extends string > (
112
118
state : TerraformState ,
113
119
type : T ,
114
120
name ?: string ,
@@ -131,12 +137,13 @@ export const findResourceInstance = <T extends "coder_script" | string>(
131
137
return resource . instances [ 0 ] . attributes as any ;
132
138
} ;
133
139
134
- // testRequiredVariables creates a test-case
135
- // for each variable provided and ensures that
136
- // the apply fails without it.
137
- export const testRequiredVariables = (
140
+ /**
141
+ * Creates a test-case for each variable provided and ensures that the apply
142
+ * fails without it.
143
+ */
144
+ export const testRequiredVariables = < TVars extends Record < string , string > > (
138
145
dir : string ,
139
- vars : Record < string , string > ,
146
+ vars : TVars ,
140
147
) => {
141
148
// Ensures that all required variables are provided.
142
149
it ( "required variables" , async ( ) => {
@@ -165,16 +172,25 @@ export const testRequiredVariables = (
165
172
} ) ;
166
173
} ;
167
174
168
- // runTerraformApply runs terraform apply in the given directory
169
- // with the given variables. It is fine to run in parallel with
170
- // other instances of this function, as it uses a random state file.
171
- export const runTerraformApply = async (
175
+ /**
176
+ * Runs terraform apply in the given directory with the given variables. It is
177
+ * fine to run in parallel with other instances of this function, as it uses a
178
+ * random state file.
179
+ */
180
+ export const runTerraformApply = async <
181
+ TVars extends Readonly < Record < string , string | boolean > > ,
182
+ > (
172
183
dir : string ,
173
- vars : Record < string , string > ,
174
- env : Record < string , string > = { } ,
184
+ vars : TVars ,
185
+ env ? : Record < string , string > ,
175
186
) : Promise < TerraformState > => {
176
187
const stateFile = `${ dir } /${ crypto . randomUUID ( ) } .tfstate` ;
177
- Object . keys ( vars ) . forEach ( ( key ) => ( env [ `TF_VAR_${ key } ` ] = vars [ key ] ) ) ;
188
+
189
+ const combinedEnv = env === undefined ? { } : { ...env } ;
190
+ for ( const [ key , value ] of Object . entries ( vars ) ) {
191
+ combinedEnv [ `TF_VAR_${ key } ` ] = String ( value ) ;
192
+ }
193
+
178
194
const proc = spawn (
179
195
[
180
196
"terraform" ,
@@ -188,22 +204,26 @@ export const runTerraformApply = async (
188
204
] ,
189
205
{
190
206
cwd : dir ,
191
- env,
207
+ env : combinedEnv ,
192
208
stderr : "pipe" ,
193
209
stdout : "pipe" ,
194
210
} ,
195
211
) ;
212
+
196
213
const text = await readableStreamToText ( proc . stderr ) ;
197
214
const exitCode = await proc . exited ;
198
215
if ( exitCode !== 0 ) {
199
216
throw new Error ( text ) ;
200
217
}
218
+
201
219
const content = await readFile ( stateFile , "utf8" ) ;
202
220
await unlink ( stateFile ) ;
203
221
return JSON . parse ( content ) ;
204
222
} ;
205
223
206
- // runTerraformInit runs terraform init in the given directory.
224
+ /**
225
+ * Runs terraform init in the given directory.
226
+ */
207
227
export const runTerraformInit = async ( dir : string ) => {
208
228
const proc = spawn ( [ "terraform" , "init" ] , {
209
229
cwd : dir ,
@@ -221,8 +241,8 @@ export const createJSONResponse = (obj: object, statusCode = 200): Response => {
221
241
"Content-Type" : "application/json" ,
222
242
} ,
223
243
status : statusCode ,
224
- } )
225
- }
244
+ } ) ;
245
+ } ;
226
246
227
247
export const writeCoder = async ( id : string , script : string ) => {
228
248
const exec = await execContainer ( id , [
0 commit comments