diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1382b90 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# http://editorconfig.org/ +# GitHub respects this file to display their diffs / code reviews if in repo root +# Seen in https://github.com/isaacs/github/issues/170 as a way to solve the 8 spaces tabs +root = true + +[*] +max_line_length = 100 # NOTE: exception to Google Style, which is generally 80 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# https://www.terraform.io/docs/configuration/style.html +[*.tf] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..d2c0e27 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +> Description here + +### Fixes +> paste links to issues/tasks in project management +- []() + +### Features +> paste links to issues/tasks in project management +- []() + +### Change implications + +- dependencies added/changed? **yes (explain) / no** diff --git a/.github/workflows/terraform_lint.yaml b/.github/workflows/terraform_lint.yaml new file mode 100644 index 0000000..765950a --- /dev/null +++ b/.github/workflows/terraform_lint.yaml @@ -0,0 +1,28 @@ +name: Terraform CI - Lint + +on: + [push] + +jobs: + tf_lint: + name: 'Terraform lint (on tf ${{ matrix.terraform_version }})' + runs-on: ubuntu-latest + strategy: + matrix: + terraform_version: ['~1.6.0', '~1.7.0', 'latest'] # in theory, we go down to 1.0, but I think that's overkill for lint + permissions: + contents: 'read' + id-token: 'write' + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: 'setup Terraform' + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: ${{ matrix.terraform_version }} + + - name: 'lint Terraform code' + # see https://www.terraform.io/cli/commands/fmt + run: + terraform fmt -check -recursive -diff . diff --git a/.github/workflows/terraform_validate.yaml b/.github/workflows/terraform_validate.yaml new file mode 100644 index 0000000..e7e9778 --- /dev/null +++ b/.github/workflows/terraform_validate.yaml @@ -0,0 +1,42 @@ +name: Terraform CI - validate + +on: + [push] + +jobs: + tf_validate: + name: 'Terraform validate (on tf ${{ matrix.terraform_version }})' + runs-on: ubuntu-latest + strategy: + matrix: + terraform_version: ['~1.6.0', '~1.7.0', 'latest'] + permissions: + contents: 'read' + id-token: 'write' + + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: 'setup Terraform' + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: ${{ matrix.terraform_version }} + + - name: 'Terraform - validate examples/create_psoxy_connections' + working-directory: examples/create_psoxy_connections + run: | + terraform init + terraform validate + + - name: 'Terraform - validate modules/sa_tenant_api_auth' + working-directory: modules/sa_tenant_api_auth + run: | + terraform init + terraform validate + + - name: 'Terraform - validate modules/psoxy_connection' + working-directory: modules/psoxy_connection + run: | + terraform init + terraform validate diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..56cf6ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +.idea + +# Local .terraform directories +**/.terraform/* + +# .tfstate files +**/*.tfstate +**/*.tfstate.* + +# Crash log files +crash.log + +# Ignore any .tfvars files that are generated automatically for each Terraform run. Most +# .tfvars files are managed as part of configuration and so should be included in +# version control. +# +# example.tfvars + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negated pattern +# +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* diff --git a/README.md b/README.md index 53ffc66..84ccd57 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,24 @@ -# terraform-gcp-worklytics -Terraform module for GCP resources to support Worklytics Data Exports and Tenant API access. +# Terraform GCP Worklytics + +A collection of Terraform submodules to support the Worklytics Tenant API using Google Cloud +Platform (GCP) resources. + +- [Tenant API: authentication via Service Account](modules/sa_tenant_api_auth) +- [Tenant API: create a Psoxy Connection](modules/psoxy_connection) + +The main use-case for these submodules is to create [Psoxy Connections] in Worklytics via its +[Tenant API]. + +After a successful Psoxy deployment (Data Source fully configured, Psoxy instance tested and +running), the last step should be to create the actual connection in the [Data Sources] section of +the Worklytics Web App. These submodules provide a way to automate this process. + +See the [examples/create_psoxy_connections] for a complete usage example. + +[Psoxy Connections]: https://docs.worklytics.co/psoxy +[Tenant API]: https://docs.worklytics.co/knowledge-base/tenant-api +[Data Sources]: https://app.worklytics.co/analytics/data-sources +[examples/create_psoxy_connections]: examples/create_psoxy_connections + + + diff --git a/examples/create_psoxy_connections/.gitignore b/examples/create_psoxy_connections/.gitignore new file mode 100644 index 0000000..05ce6b0 --- /dev/null +++ b/examples/create_psoxy_connections/.gitignore @@ -0,0 +1,9 @@ + +# ignore variable files for examples; assume people are testing in dev or something +**/terraform.tfvars + +# ignore .terraform state/lock files; again, presume people are playing with examples in dev or something +**/.terraform +**/.terraform.lock.hcl +**/TODO** + diff --git a/examples/create_psoxy_connections/README.md b/examples/create_psoxy_connections/README.md new file mode 100644 index 0000000..3eba5fb --- /dev/null +++ b/examples/create_psoxy_connections/README.md @@ -0,0 +1,28 @@ +# Create Psoxy Connections Example + +This example illustrates how to create Psoxy connections in Worklytics via its Tenant API. + +Assuming you've set up a Psoxy instance in AWS, the example will create a GCP Service Account (SA) +to get an ID Token used to authenticate with the Tenant API, and a shell script that creates a +Psoxy Connection for each one of the Data Sources configured in your Psoxy instance. + +Terraform variables used: + +```hcl +worklytics_tenant_id = "116863361842113328137" +user_principal_email = "johndoe@acme.com" +psoxy_connections = [{ + integration = "data-source-psoxy", + kind = "GCP", + endpoint = "https://us-central-1-my-worklytics-psoxy.cloudfunctions.net/data-source-psoxy", +}] +``` + +- `worklytics_tenant_id` is the unique ID of your Worklytics tenant (obtain from the Worklytics Web App). +- `service_account_id` the account ID used to generate the SA email address. This is the email that + must be registered as `DataConnectionAdmin` in the Worklytics Web App. +- `project_id` is the GCP project ID where the service account will be created. +- `psoxy_connections` is a collection of the attributes for each Data Source configured in your Psoxy instance. + +**Once the Terraform script is executed, and the shell script is created, make sure that the SA +email is registered as `DataConnectionAdmin` in the Worklytics Web App.** diff --git a/examples/create_psoxy_connections/main.tf b/examples/create_psoxy_connections/main.tf new file mode 100644 index 0000000..94749c2 --- /dev/null +++ b/examples/create_psoxy_connections/main.tf @@ -0,0 +1,28 @@ +# create the SA needed to auth with the Worklytics Tenant API +module "tenant_api_auth" { + source = "../../modules/sa_tenant_api_auth" + + service_account_id = var.service_account_id + project_id = var.project_id +} + +# create script files for each Psoxy connection +module "create_psoxy_connection_script" { + source = "../../modules/psoxy_connection" + + for_each = { + for psoxy_connection in var.psoxy_connections : + psoxy_connection.integration => psoxy_connection + } + psoxy_connection = { + integration = each.value.integration + endpoint = each.value.endpoint + bucket = each.value.bucket + parser_id = each.value.parser_id + github_organization = each.value.github_organization + } + psoxy_connection_script_path = coalesce(var.psoxy_connection_script_path, path.module) + service_account_email = module.tenant_api_auth.worklytics_tenant_api_sa + worklytics_tenant_id = var.worklytics_tenant_id + tenant_api_host = var.tenant_api_host +} diff --git a/examples/create_psoxy_connections/outputs.tf b/examples/create_psoxy_connections/outputs.tf new file mode 100644 index 0000000..a507a63 --- /dev/null +++ b/examples/create_psoxy_connections/outputs.tf @@ -0,0 +1,7 @@ +output "worklytics_tenant_api_sa" { + value = module.tenant_api_auth.worklytics_tenant_api_sa +} + +output "worklytics_tenant_api_scripts" { + value = { for key, value in module.create_psoxy_connection_script : key => value.worklytics_tenant_api_script_file.filename } +} diff --git a/examples/create_psoxy_connections/variables.tf b/examples/create_psoxy_connections/variables.tf new file mode 100644 index 0000000..10bee0c --- /dev/null +++ b/examples/create_psoxy_connections/variables.tf @@ -0,0 +1,42 @@ +variable "service_account_id" { + type = string + description = "Account ID used to generate the SA email address, 6-30 characters long." + default = "worklytics-tenant-api" +} + +variable "project_id" { + type = string + description = "ID of the GCP project where you want to create the service account." +} + +variable "worklytics_tenant_id" { + type = string + description = "Numeric ID of your Worklytics tenant's service account (obtain from Worklytics Web App)." + + validation { + condition = var.worklytics_tenant_id == null || can(regex("^\\d{21}$", var.worklytics_tenant_id)) + error_message = "`worklytics_tenant_id` must be a 21-digit numeric value. (or `null`, for pre-production use case where you don't want external entity to be allowed to assume the role)." + } +} + +variable "tenant_api_host" { + type = string + description = "Host of the Worklytics Tenant API: the domain by which Cognito will refer users." + default = "intl.worklytics.co" +} + +variable "psoxy_connections" { + type = list(object({ + integration = string # The integration ID to use for this connection. + endpoint = optional(string) # The endpoint of the Cloud Function (Work Data connections use-case). + bucket = optional(string) # The Cloud Storage bucket (Bulk Data connections use-case). + parser_id = optional(string) # Bulk Data connections only. + github_organization = optional(string) # GitHub Connections only. + })) + description = "The connection details for Psoxy connections to be created via Worklytics Tenant API." +} + +variable "psoxy_connection_script_path" { + type = string + description = "Where to create the script to create the Psoxy connection" +} diff --git a/modules/psoxy_connection/README.md b/modules/psoxy_connection/README.md new file mode 100644 index 0000000..b8578c7 --- /dev/null +++ b/modules/psoxy_connection/README.md @@ -0,0 +1,6 @@ +# Module Psoxy Connection + +This module creates a shell script that creates Psoxy Connections via the Worklytics Tenant API. + +See the [examples/create_psoxy_connections](../../examples/create_psoxy_connections) for a complete +usage example. diff --git a/modules/psoxy_connection/main.tf b/modules/psoxy_connection/main.tf new file mode 100644 index 0000000..88d51dc --- /dev/null +++ b/modules/psoxy_connection/main.tf @@ -0,0 +1,45 @@ +locals { + settings = merge( + { PROXY_DEPLOYMENT_KIND = "GCP" }, + var.psoxy_connection.bucket != null ? { PROXY_BUCKET = var.psoxy_connection.bucket } : {}, + var.psoxy_connection.endpoint != null ? { PROXY_ENDPOINT = var.psoxy_connection.endpoint } : {}, + var.psoxy_connection.parser_id != null ? { parserId = var.psoxy_connection.parser_id } : {}, + var.psoxy_connection.github_organization != null ? { GITHUB_ORGANIZATION = var.psoxy_connection.github_organization } : {} + ) + json_payload = jsonencode({ + integrationId = var.psoxy_connection.integration, + settings = local.settings + }) +} + +resource "local_file" "worklytics_tenant_api_script_file" { + content = <