Skip to content

Cloud Development Environment

This guide walks through bootstrapping an AWS account to use the FireFly Cloud deployment workflows. You will prepare the IAM policy files, create the required AWS resources, and then configure GitHub with the credentials and settings the workflows need. Once complete, the workflows will automatically create, update, and delete the CloudFormation stacks that make up the FireFly Cloud — including S3 buckets, API Gateway, DynamoDB, and Lambda functions.

This guide assumes your Route 53 is already configured for your account with a custom domain name.

AWS Region Support

Only us-east-1 region is supported.

Step 1: Prepare Policy Files

Before creating anything in AWS, update the placeholder values in the policy files in the policies/ directory:

  • AWS_ACCOUNT_ID — your AWS account ID.
  • AWS_REGION — the region you plan to deploy to.
  • S3_FIRMWARE_PRIVATE_BUCKET_NAME — the S3 bucket name you plan to use to store firmware ZIPs.
  • S3_FIRMWARE_PUBLIC_BUCKET_NAME — the S3 bucket name you plan to use for public OTA firmware delivery.
  • S3_UI_BUCKET_NAME — the S3 bucket name you plan to use for the UI static files.
  • SAM_DEPLOYMENT_BUCKET_NAME — the name of the S3 bucket where CloudFormation deployment templates will be stored.
  • HOSTED_ZONE_ID — the Hosted Zone ID for your Route 53 instance.

The following policy files require updates:

  • policies/firefly-github-actions-cloudformation-access-policy.json
  • policies/firefly-cloudformation-execution-policy.json

Step 2: AWS Setup

SAM Deployment Bucket

  1. Create an S3 bucket to store CloudFormation deployment templates. This bucket must be in the same region you plan to deploy to.
  2. Name it to match the SAM_DEPLOYMENT_BUCKET_NAME value you used in Step 1.

IAM Users

  1. Create the user that will execute the deployment and deletion of the stacks. For example, firefly-github-actions.
  2. Create security credentials for firefly-github-actions with an access key and secret.

Note

Do not create or attach permissions for the user at this time.

IAM Roles

  1. Create a new role named firefly-cloudformation-execution-role.
  2. Create a trust relationship using statements in policies/firefly-cloudformation-execution-role_trust-relationships.json.

Note

Do not create or attach permissions for the role at this time.

IAM Policies

CloudFormation Access Policy

This policy allows the IAM user to execute CloudFormation scripts and assume the CloudFormation Execution role.

  1. Create a new policy using the updated statements in policies/firefly-github-actions-cloudformation-access-policy.json.
  2. Name the policy firefly-github-actions-cloudformation-access-policy.
  3. Attach IAM user entity firefly-github-actions to the policy.

CloudFormation Execution Policy

This policy allows CloudFormation to deploy and delete the individual AWS services in each stack.

  1. Create a new policy using the updated statements in policies/firefly-cloudformation-execution-policy.json.
  2. Name the policy firefly-cloudformation-execution-policy.
  3. Attach IAM role entity firefly-cloudformation-execution-role to the policy.

Step 3: GitHub Setup

GitHub Environments

The workflows deploy to either a dev or production environment. Create both environments in the repository settings under Settings > Environments.

Note

Secrets and variables must be set at the environment level, not at the repository level.

GitHub Secrets

The following secrets must be configured in each GitHub environment:

NameExample ValueDescription
AWS_ACCESS_KEY_IDfirefly-github-actionsThe access key for IAM user.
AWS_ACCOUNT_ID1234567890Your AWS account ID.
AWS_REGIONus-east-1The AWS region you plan to deploy to.
AWS_SECRET_ACCESS_KEYThe access key secret for IAM user firefly-github-actions.
GOOGLE_CLIENT_IDOAuth 2.0 Client ID from Google Cloud Console. See Google Cloud Setup.
GOOGLE_CLIENT_SECRETOAuth 2.0 Client Secret from Google Cloud Console. See Google Cloud Setup.
HOSTED_ZONE_IDAB1234567The Hosted Zone ID for your Route 53 instance.
S3_FIRMWARE_PRIVATE_BUCKET_NAMEmy-firmware-privateThe S3 bucket name for storing firmware ZIPs (private).
S3_FIRMWARE_PUBLIC_BUCKET_NAMEmy-firmware-publicThe S3 bucket name for OTA firmware binary delivery (public).
S3_UI_BUCKET_NAMEmy-firefly-uiThe S3 bucket name for the UI static files (private, served via CloudFront).
SAM_DEPLOYMENT_BUCKET_NAMEmy-sam-deployment-bucketThe name of the bucket where deployment templates will be stored.

GitHub Variables

The following variables must be configured in each GitHub environment:

NameExample ValueDescription
API_DOMAIN_NAMEapi.somewhere.comThe domain name for the API gateway.
API_URLhttps://api.somewhere.comThe full base URL for the API, including the https:// scheme, injected into the UI at build time.
CERTIFICATE_DOMAIN_NAME*.somewhere.comA wildcard to your domain.
CLOUD_FORMATION_EXECUTION_ROLE_NAMEfirefly-cloudformation-execution-roleName of the execution role.
AUTH_DOMAIN_NAMEauth.somewhere.comThe custom domain for the Cognito hosted UI (e.g., auth.example.com). A Route 53 alias record is created automatically during deployment.
DYNAMODB_FIRMWARE_TABLE_NAMEfirefly-firmwareThe name of the firmware table.
DYNAMODB_USERS_TABLE_NAMEfirefly-usersThe name of the users allowed-list table.
FIRMWARE_DOMAIN_NAMEfirmware.somewhere.comThe domain name for the CloudFront firmware distribution.
FIRMWARE_TYPE_MAP{"Controller":"FireFly Controller"}JSON mapping from URL application name to the firmware type string expected by the device.
UI_DOMAIN_NAMEui.somewhere.comThe custom domain name for the firmware management UI, without the https:// scheme.

Google Cloud Setup

FireFly uses Google as the only identity provider for the management console. This is a one-time setup per Google account.

  1. Go to the Google Cloud Console and create a new project (e.g., firefly-auth).
  2. In the left menu, go to APIs & ServicesOAuth consent screen.
    • Choose External user type.
    • Fill in the required fields (app name, support email, developer contact).
    • Add the scope openid, email, and profile.
    • Add your Google accounts to the Test users list while the app is in testing mode.
  3. Go to APIs & ServicesCredentialsCreate CredentialsOAuth 2.0 Client ID.
    • Application type: Web application.
    • Under Authorized redirect URIs, add the Cognito hosted UI callback URL:
      https://{AUTH_DOMAIN_NAME}/oauth2/idpresponse
      Replace {AUTH_DOMAIN_NAME} with the value of the AUTH_DOMAIN_NAME GitHub variable (e.g., auth.example.com).
  4. Copy the Client ID and Client Secret — these become the GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET GitHub secrets.

Redirect URI

The redirect URI must be added exactly as shown, including the /oauth2/idpresponse path. Cognito handles the redirect; the SPA callback URL is separate and configured in the User Pool client.

Adding the First Super User

After deploying the Cognito stack, add the first super user manually before using the UI. See Administration → Adding the First Super User.

GitHub Actions Workflows

All deployments and deletions are performed through GitHub Actions workflows, each targeting either the dev or production environment. Most individual workflows also include an optional Run integration tests after deploy checkbox.

Deploying

For initial setup, use Deploy All (deploy-all), which deploys all stacks in the correct dependency order and runs integration tests at the end.

Individual deploy workflows are available for updating a specific stack without redeploying everything:

WorkflowDescription
deploy-allDeploys all stacks in dependency order and runs integration tests. Use this for first-time setup.
deploy-dynamodb-firmwareCreates the DynamoDB firmware table.
deploy-dynamodb-usersCreates the DynamoDB users allowed-list table.
deploy-func-cognito-pre-signupDeploys the Cognito pre-signup Lambda. Requires the users DynamoDB table.
deploy-cognitoDeploys the Cognito User Pool with Google IdP. Requires the pre-signup Lambda.
deploy-acmRequests the ACM certificate and validates it via Route 53. Must run before API Gateway, CloudFront, and Cognito.
deploy-api-gatewayDeploys the HTTP API Gateway with JWT authorizer. Requires ACM certificate and Cognito User Pool.
deploy-shared-layerPublishes the shared Lambda layer used by most functions.
deploy-func-api-health-getDeploys the health check Lambda. Requires API Gateway.
deploy-func-api-firmware-getDeploys the firmware list/item Lambda. Requires API Gateway and shared layer.
deploy-func-api-users-getDeploys the users list Lambda. Requires API Gateway and Cognito.
deploy-func-api-users-postDeploys the user invite Lambda. Requires API Gateway and users table.
deploy-func-api-users-deleteDeploys the user delete Lambda. Requires API Gateway, Cognito, and users table.
deploy-func-api-users-patchDeploys the user super-status Lambda. Requires API Gateway and Cognito.
deploy-func-api-firmware-status-patchDeploys the firmware status update Lambda. Requires API Gateway and shared layer.
deploy-func-api-firmware-deleteDeploys the firmware delete Lambda. Requires API Gateway and shared layer.
deploy-func-s3-firmware-uploadedDeploys the S3 upload trigger Lambda. Requires shared layer.
deploy-func-s3-firmware-deletedDeploys the S3 delete trigger Lambda. Requires shared layer.
deploy-s3-firmwareCreates the private S3 firmware bucket and wires up event notifications. Requires both S3 trigger Lambdas.
deploy-s3-firmware-publicCreates the public S3 bucket for OTA firmware delivery.
deploy-cloudfront-firmwareDeploys the CloudFront distribution. Requires ACM certificate and public S3 bucket.
deploy-func-api-ota-getDeploys the OTA firmware manifest Lambda. Requires API Gateway, shared layer, and CloudFront.
deploy-func-api-firmware-download-getDeploys the pre-signed download URL Lambda. Requires API Gateway, shared layer, and private S3 firmware bucket.
deploy-s3-uiCreates the private S3 bucket for UI static files.
deploy-cloudfront-uiDeploys the CloudFront distribution serving the UI. Requires ACM certificate and UI S3 bucket.
deploy-ui-appBuilds the Vue app and syncs it to S3, then invalidates the CloudFront cache. Requires CloudFront UI distribution.

Deleting

Use Delete All (delete-all) to tear down the entire environment. Individual delete workflows are available if you need to remove a specific stack.

Dependency Order

Stacks must be deleted in reverse dependency order. Delete All handles this automatically. If running individual delete workflows manually, delete Lambda functions before the API Gateway, the API Gateway before the ACM certificate, the S3 bucket before the S3 trigger Lambdas, and all Lambda functions before the shared layer.

WorkflowDescription
delete-allTears down all stacks in the correct order.
delete-s3-firmwareDeletes the private S3 firmware bucket stack. Must run before the S3 trigger Lambdas.
delete-func-api-ota-getDeletes the OTA firmware manifest Lambda. Must run before the API Gateway.
delete-cloudfront-firmwareDeletes the CloudFront distribution. Must run before the public S3 bucket.
delete-s3-firmware-publicDeletes the public S3 firmware bucket. Must run after CloudFront is deleted.
delete-func-api-health-getDeletes the health check Lambda.
delete-func-api-firmware-getDeletes the firmware list/download Lambda.
delete-func-api-firmware-status-patchDeletes the firmware status update Lambda.
delete-func-api-firmware-deleteDeletes the firmware delete Lambda.
delete-api-gatewayDeletes the API Gateway. Must run after all API Lambda functions are deleted.
delete-acmDeletes the ACM certificate. Must run after the API Gateway, both CloudFront distributions, and Cognito are deleted.
delete-func-s3-firmware-uploadedDeletes the S3 upload trigger Lambda. Must run after the S3 bucket is deleted.
delete-func-s3-firmware-deletedDeletes the S3 delete trigger Lambda. Must run after the S3 bucket is deleted.
delete-shared-layerDeletes the shared Lambda layer. Must run after all Lambda functions are deleted.
delete-dynamodb-firmwareDeletes the DynamoDB firmware table.
delete-func-api-firmware-download-getDeletes the pre-signed download URL Lambda. Must run before the API Gateway.
delete-ui-appEmpties the UI S3 bucket. Must run before the CloudFront UI distribution is deleted.
delete-cloudfront-uiDeletes the CloudFront UI distribution. Must run after the UI S3 bucket is emptied.
delete-s3-uiDeletes the private UI S3 bucket. Must run after the CloudFront UI distribution is deleted.

Integration Tests

The Run Integration Tests (run-integration-tests) workflow can be run independently to validate a deployed environment without making any changes. It looks up the API URL from the firefly-api-gateway stack output and the UI URL from the firefly-cloudfront-ui stack output, then runs the full test suite against the live environment. UI tests are automatically skipped if the firefly-cloudfront-ui stack does not exist.

Running Tests Locally

Integration tests can also be run locally against any deployed environment.

Setup

bash
pip install -r tests/requirements.txt

Environment Variables

VariableRequiredDescription
FIREFLY_API_URLNoAPI base URL (defaults to the production URL if not set)
FIREFLY_FIRMWARE_BUCKETFor upload testsPrivate S3 firmware bucket name
FIREFLY_UI_URLFor UI and CORS testsBase URL of the firmware management UI (e.g. https://ui.example.com)
FIREFLY_UI_BUCKETFor UI S3 testsName of the private S3 bucket serving the UI static files
FIREFLY_COGNITO_USER_POOL_IDFor auth testsCognito User Pool ID
FIREFLY_COGNITO_CLIENT_IDFor auth testsCognito App Client ID
FIREFLY_TEST_USER_EMAILFor auth testsEmail of a Cognito user created via AdminCreateUser; in CI this is generated automatically each run
FIREFLY_TEST_USER_PASSWORDFor auth testsPassword of the test Cognito user; in CI this is generated automatically each run

Authentication tests are automatically skipped when the Cognito environment variables are not set. All other tests pass auth headers when the variables are present, allowing the full test suite to run against a deployed environment that requires authentication.

In CI, the workflow generates a unique email and password at runtime, creates the user, runs tests, then deletes the user unconditionally — no persistent secrets required. For local test runs, set these variables to a manually created Cognito user.

The /users endpoint tests require additional Cognito admin permissions: the test runner's IAM identity must have cognito-idp:AdminAddUserToGroup, cognito-idp:AdminRemoveUserFromGroup, and cognito-idp:ListUsers on the user pool. The test suite temporarily adds the CI test user to the super_users group to exercise those endpoints, then removes them at teardown.

AWS credentials must be available via the standard boto3 credential chain.

Running Tests

bash
# All tests
pytest tests/integration/ -v

# Skip tests that require S3 upload
pytest tests/integration/ -v -k "not (firmware_item or fresh_firmware_item)"

Tests that upload firmware wait up to 60 seconds for the S3 event to propagate and the record to appear in the API before proceeding.