@@ -29,8 +29,10 @@ export const runContainer = async (
2929 return containerID . trim ( ) ;
3030} ;
3131
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+ */
3436export const executeScriptInContainer = async (
3537 state : TerraformState ,
3638 image : string ,
@@ -76,27 +78,30 @@ export const execContainer = async (
7678 } ;
7779} ;
7880
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+
7996export interface TerraformState {
8097 outputs : {
8198 [ key : string ] : {
8299 type : string ;
83100 value : any ;
84101 } ;
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 [ ] ] ;
100105}
101106
102107export interface CoderScriptAttributes {
@@ -105,10 +110,11 @@ export interface CoderScriptAttributes {
105110 url : string ;
106111}
107112
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 > (
112118 state : TerraformState ,
113119 type : T ,
114120 name ?: string ,
@@ -131,12 +137,13 @@ export const findResourceInstance = <T extends "coder_script" | string>(
131137 return resource . instances [ 0 ] . attributes as any ;
132138} ;
133139
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 > > (
138145 dir : string ,
139- vars : Record < string , string > ,
146+ vars : TVars ,
140147) => {
141148 // Ensures that all required variables are provided.
142149 it ( "required variables" , async ( ) => {
@@ -165,16 +172,25 @@ export const testRequiredVariables = (
165172 } ) ;
166173} ;
167174
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+ > (
172183 dir : string ,
173- vars : Record < string , string > ,
174- env : Record < string , string > = { } ,
184+ vars : TVars ,
185+ env ? : Record < string , string > ,
175186) : Promise < TerraformState > => {
176187 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+
178194 const proc = spawn (
179195 [
180196 "terraform" ,
@@ -188,22 +204,26 @@ export const runTerraformApply = async (
188204 ] ,
189205 {
190206 cwd : dir ,
191- env,
207+ env : combinedEnv ,
192208 stderr : "pipe" ,
193209 stdout : "pipe" ,
194210 } ,
195211 ) ;
212+
196213 const text = await readableStreamToText ( proc . stderr ) ;
197214 const exitCode = await proc . exited ;
198215 if ( exitCode !== 0 ) {
199216 throw new Error ( text ) ;
200217 }
218+
201219 const content = await readFile ( stateFile , "utf8" ) ;
202220 await unlink ( stateFile ) ;
203221 return JSON . parse ( content ) ;
204222} ;
205223
206- // runTerraformInit runs terraform init in the given directory.
224+ /**
225+ * Runs terraform init in the given directory.
226+ */
207227export const runTerraformInit = async ( dir : string ) => {
208228 const proc = spawn ( [ "terraform" , "init" ] , {
209229 cwd : dir ,
@@ -221,8 +241,8 @@ export const createJSONResponse = (obj: object, statusCode = 200): Response => {
221241 "Content-Type" : "application/json" ,
222242 } ,
223243 status : statusCode ,
224- } )
225- }
244+ } ) ;
245+ } ;
226246
227247export const writeCoder = async ( id : string , script : string ) => {
228248 const exec = await execContainer ( id , [
0 commit comments