Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

S177 initial version #1

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -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**
28 changes: 28 additions & 0 deletions .github/workflows/terraform_lint.yaml
Original file line number Diff line number Diff line change
@@ -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 .
42 changes: 42 additions & 0 deletions .github/workflows/terraform_validate.yaml
Original file line number Diff line number Diff line change
@@ -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
31 changes: 31 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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*
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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



9 changes: 9 additions & 0 deletions examples/create_psoxy_connections/.gitignore
Original file line number Diff line number Diff line change
@@ -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**

28 changes: 28 additions & 0 deletions examples/create_psoxy_connections/README.md
Original file line number Diff line number Diff line change
@@ -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 = "[email protected]"
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.**
28 changes: 28 additions & 0 deletions examples/create_psoxy_connections/main.tf
Original file line number Diff line number Diff line change
@@ -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
}
7 changes: 7 additions & 0 deletions examples/create_psoxy_connections/outputs.tf
Original file line number Diff line number Diff line change
@@ -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 }
}
42 changes: 42 additions & 0 deletions examples/create_psoxy_connections/variables.tf
Original file line number Diff line number Diff line change
@@ -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"
}
6 changes: 6 additions & 0 deletions modules/psoxy_connection/README.md
Original file line number Diff line number Diff line change
@@ -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.
45 changes: 45 additions & 0 deletions modules/psoxy_connection/main.tf
Original file line number Diff line number Diff line change
@@ -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 = <<EOT
#!/bin/bash

# Script to set up a Psoxy connection for ${var.psoxy_connection.integration}

SERVICE_ACCOUNT="${var.service_account_email}"

# If service account (SA) is not provided, don't impersonate and assume that current credentials
# are already set for a valid SA
if [ -z "$SERVICE_ACCOUNT" ]; then
TOKEN=`gcloud auth print-identity-token --audiences="https://app.worklytics.co"`
else
TOKEN=`gcloud auth print-identity-token \
--impersonate-service-account=$SERVICE_ACCOUNT \
--audiences="https://app.worklytics.co" \
--include-email`
fi

echo "ID Token Info:"
curl "https://oauth2.googleapis.com/tokeninfo?id_token=$TOKEN"

curl -X POST https://${var.tenant_api_host}/tenant-api/data-connections \
-H "Authorization: Bearer $TOKEN" \
-H "x-worklytics-tenant-id: ${var.worklytics_tenant_id}"\
-H "Content-Type: application/json" \
-d "${local.json_payload}"
EOT
filename = "${coalesce(var.psoxy_connection_script_path, path.module)}/create_${var.psoxy_connection.integration}_connection.sh"
file_permission = "0755"
}
4 changes: 4 additions & 0 deletions modules/psoxy_connection/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
output "worklytics_tenant_api_script_file" {
description = "The generated script to invoke the Tenant API"
value = local_file.worklytics_tenant_api_script_file
}
42 changes: 42 additions & 0 deletions modules/psoxy_connection/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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"
default = "intl.worklytics.co"
}

variable "service_account_email" {
type = string
description = "Email of the GCP service account that has been granted access to the Worklytics Tenant API."
default = null

validation {
condition = var.service_account_email == null || can(regex(".*@.*\\.iam\\.gserviceaccount\\.com$", var.service_account_email))
error_message = "The service_account_email value should be a valid GCP service account email address."
}
}

variable "psoxy_connection" {
type = 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 a Psoxy connection 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"
}
14 changes: 14 additions & 0 deletions modules/sa_tenant_api_auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Module Service Account Tenant API Auth

To authenticate with the Worklytics Tenant API, you need a Google Identity Token (ID Token) that
Worklytics can verify to identify the user calling any of the Tenant API endpoints. As the
[Tenant API documentation] details, the recommended way to obtain the ID Token, is using a Service
Account (SA). Using this module, you can create that SA with the required roles.

See the [examples/create_psoxy_connections](../../examples/create_psoxy_connections) for a complete
usage example.

[Tenant API documentation]: https://docs.worklytics.co/knowledge-base/tenant-api#authentication



Loading