-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(deploy): add the terraform deployment
- Loading branch information
Showing
6 changed files
with
404 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
import { extra, autoService } from 'knifecycle'; | ||
import { readArgs } from '@whook/whook'; | ||
import { flattenOpenAPI, getOpenAPIOperations } from '@whook/http-router'; | ||
import { YError } from 'yerror'; | ||
import { exec } from 'child_process'; | ||
import crypto from 'crypto'; | ||
import yaml from 'js-yaml'; | ||
import type { ExecException } from 'child_process'; | ||
import type { LogService } from 'common-services'; | ||
import type { | ||
WhookCommandArgs, | ||
WhookCommandDefinition, | ||
WhookAPIHandlerDefinition, | ||
} from '@whook/whook'; | ||
import type { OpenAPIV3 } from 'openapi-types'; | ||
|
||
export const definition: WhookCommandDefinition = { | ||
description: 'A command printing functions informations for Terraform', | ||
example: `whook terraformValues --type paths`, | ||
arguments: { | ||
type: 'object', | ||
additionalProperties: false, | ||
required: ['type'], | ||
properties: { | ||
type: { | ||
description: 'Type of values to return', | ||
type: 'string', | ||
enum: ['globals', 'paths', 'functions', 'function'], | ||
}, | ||
pretty: { | ||
description: 'Pretty print JSON values', | ||
type: 'boolean', | ||
}, | ||
functionName: { | ||
description: 'Name of the function', | ||
type: 'string', | ||
}, | ||
pathsIndex: { | ||
description: 'Index of the paths to retrieve', | ||
type: 'number', | ||
}, | ||
functionType: { | ||
description: 'Types of the functions to return', | ||
type: 'string', | ||
}, | ||
}, | ||
}, | ||
}; | ||
|
||
export default extra(definition, autoService(initTerraformValuesCommand)); | ||
|
||
async function initTerraformValuesCommand({ | ||
API, | ||
BASE_PATH, | ||
log, | ||
args, | ||
execAsync = _execAsync, | ||
}: { | ||
API: OpenAPIV3.Document; | ||
BASE_PATH: string; | ||
log: LogService; | ||
args: WhookCommandArgs; | ||
execAsync: typeof _execAsync; | ||
}) { | ||
return async () => { | ||
const { | ||
namedArguments: { type, pretty, functionName, functionType }, | ||
} = readArgs<{ | ||
type: string; | ||
pretty: boolean; | ||
functionName: string; | ||
functionType: string; | ||
}>(definition.arguments, args); | ||
const operations = getOpenAPIOperations< | ||
WhookAPIHandlerDefinition['operation']['x-whook'] | ||
>(await flattenOpenAPI(API)); | ||
const configurations = operations.map((operation) => { | ||
const whookConfiguration = (operation['x-whook'] || { | ||
type: 'http', | ||
}) as WhookAPIHandlerDefinition['operation']['x-whook']; | ||
const configuration = { | ||
type: 'http', | ||
timeout: '10', | ||
memory: '128', | ||
description: operation.summary || '', | ||
enabled: 'true', | ||
sourceOperationId: operation.operationId, | ||
suffix: '', | ||
...Object.keys(whookConfiguration || {}).reduce( | ||
(accConfigurations, key) => ({ | ||
...accConfigurations, | ||
[key]: ( | ||
( | ||
whookConfiguration as NonNullable< | ||
WhookAPIHandlerDefinition['operation']['x-whook'] | ||
> | ||
)[key] as string | ||
).toString(), | ||
}), | ||
{}, | ||
), | ||
}; | ||
const qualifiedOperationId = | ||
(configuration.sourceOperationId || operation.operationId) + | ||
(configuration.suffix || ''); | ||
|
||
return { | ||
qualifiedOperationId, | ||
method: operation.method.toUpperCase(), | ||
path: operation.path, | ||
...configuration, | ||
}; | ||
}); | ||
|
||
if (type === 'globals') { | ||
const commitHash = await execAsync(`git rev-parse HEAD`); | ||
const commitMessage = ( | ||
await execAsync(`git rev-list --format=%B --max-count=1 HEAD`) | ||
).split('\n')[1]; | ||
const openapi2 = yaml.safeDump({ | ||
swagger: '2.0', | ||
info: { | ||
title: API.info.title, | ||
description: API.info.description, | ||
version: API.info.version, | ||
}, | ||
host: '${infos_host}', | ||
basePath: BASE_PATH, | ||
schemes: ['https'], | ||
produces: ['application/json'], | ||
paths: configurations.reduce((accPaths, configuration) => { | ||
const operation = operations.find( | ||
({ operationId }) => | ||
operationId === configuration.sourceOperationId, | ||
); | ||
|
||
return { | ||
...accPaths, | ||
[configuration.path]: { | ||
...(accPaths[configuration.path] || {}), | ||
[configuration.method.toLowerCase()]: { | ||
summary: configuration.description || '', | ||
operationId: configuration.qualifiedOperationId, | ||
...((operation?.parameters || []).length | ||
? { | ||
parameters: ( | ||
operation?.parameters as OpenAPIV3.ParameterObject[] | ||
).map(({ in: theIn, name, required }) => ({ | ||
in: theIn, | ||
name, | ||
type: 'string', | ||
required: required || false, | ||
})), | ||
} | ||
: undefined), | ||
'x-google-backend': { | ||
address: `\${function_${configuration.qualifiedOperationId}}`, | ||
}, | ||
responses: { | ||
'200': { description: 'x', schema: { type: 'string' } }, | ||
}, | ||
}, | ||
}, | ||
}; | ||
}, {}), | ||
}); | ||
const openapiHash = crypto | ||
.createHash('md5') | ||
.update(JSON.stringify(API)) | ||
.digest('hex'); | ||
const infos = { | ||
commitHash, | ||
commitMessage, | ||
openapi2, | ||
openapiHash, | ||
}; | ||
log('info', JSON.stringify(infos)); | ||
return; | ||
} | ||
|
||
if (type === 'functions') { | ||
const functions = configurations | ||
.filter((configuration) => | ||
functionType ? configuration.type === functionType : true, | ||
) | ||
.reduce( | ||
(accLambdas, configuration) => ({ | ||
...accLambdas, | ||
[configuration.qualifiedOperationId]: | ||
configuration.qualifiedOperationId, | ||
}), | ||
{}, | ||
); | ||
|
||
log('info', `${JSON.stringify(functions, null, pretty ? 2 : 0)}`); | ||
return; | ||
} | ||
|
||
if (!functionName) { | ||
throw new YError('E_FUNCTION_NAME_REQUIRED'); | ||
} | ||
|
||
const functionConfiguration = configurations.find( | ||
({ qualifiedOperationId }) => qualifiedOperationId === functionName, | ||
); | ||
|
||
log( | ||
'info', | ||
`${JSON.stringify(functionConfiguration, null, pretty ? 2 : 0)}`, | ||
); | ||
}; | ||
} | ||
|
||
async function _execAsync(command: string): Promise<string> { | ||
return await new Promise((resolve, reject) => { | ||
exec( | ||
command, | ||
(err: ExecException | null, stdout: string, stderr: string) => { | ||
if (err) { | ||
reject(YError.wrap(err, 'E_EXEC_FAILURE', stderr)); | ||
return; | ||
} | ||
resolve(stdout.trim()); | ||
}, | ||
); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
data "external" "functionConfiguration" { | ||
for_each = data.external.functions.result | ||
|
||
program = ["env", "APP_ENV=${terraform.workspace}", "NODE_ENV=${var.node_env}", "npx", "whook", "terraformValues", "--type='function'", "--functionName='${each.key}'"] | ||
} | ||
|
||
resource "google_storage_bucket" "storage_bucket" { | ||
name = "whook_functions" | ||
} | ||
|
||
data "archive_file" "functions" { | ||
for_each = data.external.functions.result | ||
|
||
type = "zip" | ||
source_dir = "./builds/${terraform.workspace}/${each.key}" | ||
output_path = "./builds/${terraform.workspace}/${each.key}.zip" | ||
} | ||
|
||
resource "google_storage_bucket_object" "storage_bucket_object" { | ||
for_each = data.external.functions.result | ||
|
||
name = "${terraform.workspace}_${each.key}" | ||
source = "./builds/${terraform.workspace}/${each.key}.zip" | ||
bucket = google_storage_bucket.storage_bucket.name | ||
depends_on = [data.archive_file.functions] | ||
} | ||
|
||
resource "google_cloudfunctions_function" "cloudfunctions_function" { | ||
for_each = data.external.functions.result | ||
|
||
name = "${terraform.workspace}_${each.key}" | ||
description = data.external.functionConfiguration[each.key].result["description"] | ||
runtime = "nodejs10" | ||
|
||
available_memory_mb = data.external.functionConfiguration[each.key].result["memory"] | ||
timeout = data.external.functionConfiguration[each.key].result["timeout"] | ||
source_archive_bucket = google_storage_bucket.storage_bucket.name | ||
source_archive_object = google_storage_bucket_object.storage_bucket_object[each.key].name | ||
trigger_http = true | ||
entry_point = "default" | ||
} | ||
|
||
# Seems to not work (no idea why) | ||
# resource "google_cloudfunctions_function_iam_member" "invoker" { | ||
# for_each = data.external.functions.result | ||
|
||
# project = google_cloudfunctions_function.cloudfunctions_function[each.key].project | ||
# region = google_cloudfunctions_function.cloudfunctions_function[each.key].region | ||
# cloud_function = google_cloudfunctions_function.cloudfunctions_function[each.key].name | ||
|
||
# role = "roles/cloudfunctions.invoker" | ||
# member = "allUsers" | ||
# } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
provider "google" { | ||
version = "~> 3.14" | ||
project = var.project_id | ||
region = var.region | ||
zone = var.zone | ||
credentials = file(".credentials.json") | ||
} | ||
|
||
provider "archive" { | ||
version = "~> 1.3" | ||
} | ||
|
||
provider "template" { | ||
version = "~> 2.1.2" | ||
} | ||
|
||
output "api_url" { | ||
value = google_endpoints_service.endpoints_service.dns_address | ||
} | ||
|
||
data "google_project" "project" { | ||
project_id = var.project_id | ||
} | ||
|
||
# imports the functions list | ||
data "external" "functions" { | ||
program = ["env", "NODE_ENV=${terraform.workspace}", "npx", "whook", "terraformValues", "--type='functions'", "--functionType='http'"] | ||
} | ||
data "external" "globals" { | ||
program = ["env", "NODE_ENV=${terraform.workspace}", "npx", "whook", "terraformValues", "--type='globals'"] | ||
} | ||
|
||
data "template_file" "template_file" { | ||
template = data.external.globals.result["openapi2"] | ||
|
||
vars = merge({ | ||
"infos_host" : "${var.api_name}.endpoints.${data.google_project.project.project_id}.cloud.goog" | ||
}, zipmap( | ||
[for key in keys(data.external.functions.result) : "function_${key}"], | ||
[for key in keys(data.external.functions.result) : google_cloudfunctions_function.cloudfunctions_function[key].https_trigger_url] | ||
)) | ||
} | ||
|
||
resource "google_endpoints_service" "endpoints_service" { | ||
service_name = "${var.api_name}.endpoints.${data.google_project.project.project_id}.cloud.goog" | ||
project = data.google_project.project.project_id | ||
openapi_config = data.template_file.template_file.rendered | ||
} |
Oops, something went wrong.