Ensure device logs always shown #3544
Workflow file for this run
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
name: Create pre-staging environment | |
on: | |
workflow_dispatch: | |
inputs: | |
pr_number: | |
description: 'Pull request number' | |
required: true | |
driver_k8s_branch: | |
description: 'flowfuse/driver-k8s branch name' | |
required: true | |
default: 'main' | |
nr_launcher_branch: | |
description: 'flowfuse/nr-launcher branch name' | |
required: true | |
default: 'main' | |
pull_request: | |
types: | |
- opened | |
- synchronize | |
- reopened | |
- closed | |
paths-ignore: | |
- 'docs/**' | |
concurrency: | |
group: ${{ github.workflow }}-${{ github.event.number }} | |
cancel-in-progress: false | |
jobs: | |
# This job validates if the user, who triggered the workflow, is a member of the organization | |
# Note: Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges. | |
validate-user: | |
name: Validate trigger author | |
runs-on: ubuntu-latest | |
if: | | |
(github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch') && | |
github.actor != 'dependabot[bot]' | |
outputs: | |
is_org_member: ${{ steps.validate.outputs.is_member }} | |
steps: | |
- name: Generate a token | |
id: generate_token | |
uses: tibdex/github-app-token@v2 | |
with: | |
app_id: ${{ secrets.GH_BOT_APP_ID }} | |
private_key: ${{ secrets.GH_BOT_APP_KEY }} | |
- name: Validate | |
id: validate | |
run: | | |
if [ "${{ github.actor }}" == 'dependabot[bot]' ]; then | |
echo "is_member=false" >> $GITHUB_OUTPUT | |
exit 0 | |
fi | |
member_status=$(gh api orgs/flowfuse/memberships/${{ github.actor }} -q '.state') | |
if [ "${member_status}" == "active" ]; then | |
echo "is_member=true" >> $GITHUB_OUTPUT | |
else | |
echo "is_member=false" >> $GITHUB_OUTPUT | |
fi | |
env: | |
GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
publish_k8s_driver: | |
name: Build and publish kubernetes driver | |
needs: validate-user | |
if: | | |
needs.validate-user.outputs.is_org_member == 'true' && | |
github.event_name == 'workflow_dispatch' && | |
github.event.action != 'closed' && | |
inputs.driver_k8s_branch != 'main' | |
uses: 'flowfuse/github-actions-workflows/.github/workflows/publish_node_package.yml@v0.38.0' | |
with: | |
package_name: driver-k8s | |
publish_package: true | |
repository_name: 'FlowFuse/driver-k8s' | |
branch_name: ${{ inputs.driver_k8s_branch }} | |
release_name: "pre-staging-${{ inputs.driver_k8s_branch }}" | |
secrets: | |
npm_registry_token: ${{ secrets.NPM_PUBLISH_TOKEN }} | |
publish_nr_launcher: | |
name: Build and publish Node-RED launcher package | |
needs: validate-user | |
if: | | |
needs.validate-user.outputs.is_org_member == 'true' && | |
github.event_name == 'workflow_dispatch' && | |
github.event.action != 'closed' && | |
inputs.nr_launcher_branch != 'main' | |
uses: 'flowfuse/github-actions-workflows/.github/workflows/publish_node_package.yml@v0.38.0' | |
with: | |
package_name: flowfuse-nr-launcher | |
publish_package: true | |
repository_name: 'FlowFuse/nr-launcher' | |
branch_name: ${{ inputs.nr_launcher_branch }} | |
release_name: "pre-staging-${{ inputs.nr_launcher_branch }}" | |
secrets: | |
npm_registry_token: ${{ secrets.NPM_PUBLISH_TOKEN }} | |
build-node-red: | |
name: Build Node-RED 4.0.x container images | |
needs: | |
- validate-user | |
- publish_nr_launcher | |
if: | | |
needs.validate-user.outputs.is_org_member == 'true' && | |
github.event_name == 'workflow_dispatch' && | |
github.event.action != 'closed' && | |
(always() && needs.publish_nr_launcher.result != 'failure') | |
uses: flowfuse/github-actions-workflows/.github/workflows/build_container_image.yml@v0.38.0 | |
with: | |
image_name: 'node-red' | |
dockerfile_path: Dockerfile | |
image_tag_prefix: '4.0.x-' | |
package_dependencies: | | |
@flowforge/nr-project-nodes=nightly | |
build_context: './ci/node-red' | |
build_arguments: | | |
BUILD_TAG=${{ needs.publish_nr_launcher.outputs.release_name }} | |
build_platform: "linux/arm64" | |
npm_registry_url: ${{ vars.PUBLIC_NPM_REGISTRY_URL }} | |
secrets: | |
temporary_registry_token: ${{ secrets.GITHUB_TOKEN }} | |
upload-node-red: | |
name: Publish Node-RED 4.0.x container images | |
needs: | |
- validate-user | |
- build-node-red | |
if: | | |
needs.validate-user.outputs.is_org_member == 'true' && | |
github.event_name == 'workflow_dispatch' && | |
github.event.action != 'closed' | |
runs-on: ubuntu-latest | |
environment: staging | |
env: | |
IMAGE_NAME: 'node-red' | |
PR_NUMBER: ${{ github.event.number == '' && inputs.pr_number || github.event.number }} | |
outputs: | |
nr_custom_image_tag: ${{ steps.set_outputs.outputs.nr_image_tag }} | |
steps: | |
- name: Set variables | |
run: | | |
echo "tagged_image=${{ env.IMAGE_NAME }}:4.0.x-pr-${{ env.PR_NUMBER }}" >> $GITHUB_ENV | |
echo "timestamp=$(date +%s)" >> $GITHUB_ENV | |
- name: Setup Docker buildx | |
uses: docker/setup-buildx-action@v3 | |
- name: Configure AWS credentials for ECR interaction | |
id: aws-config | |
uses: aws-actions/configure-aws-credentials@v4 | |
with: | |
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
aws-secret-access-key: ${{ secrets.AWS_ACCESS_KEY_SECRET }} | |
aws-region: eu-west-1 | |
mask-aws-account-id: true | |
- name: Login to AWS ECR | |
id: login-ecr | |
uses: aws-actions/amazon-ecr-login@v2 | |
with: | |
mask-password: true | |
- name: Login to GHCR | |
uses: docker/login-action@v3 | |
with: | |
registry: ghcr.io | |
username: ${{ github.actor }} | |
password: ${{ secrets.GITHUB_TOKEN }} | |
- name: Push to ECR | |
id: image-push | |
run: | | |
docker pull ${{ needs.build-node-red.outputs.image }} | |
docker tag ${{ needs.build-node-red.outputs.image }} ${{ steps.aws-config.outputs.aws-account-id }}.dkr.ecr.eu-west-1.amazonaws.com/flowforge/${{ env.tagged_image }}-${{ env.timestamp }} | |
docker push ${{ steps.aws-config.outputs.aws-account-id }}.dkr.ecr.eu-west-1.amazonaws.com/flowforge/${{ env.tagged_image }}-${{ env.timestamp }} | |
- name: Set outputs | |
id: set_outputs | |
run: | | |
echo "nr_image_tag=${{ env.tagged_image }}-${{ env.timestamp }}" >> $GITHUB_OUTPUT | |
build: | |
name: Build and contenerize | |
needs: | |
- validate-user | |
- publish_k8s_driver | |
if: | | |
needs.validate-user.outputs.is_org_member == 'true' && | |
(always() && needs.publish_k8s_driver.result != 'failure') && | |
(github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch') && | |
github.event.action != 'closed' | |
runs-on: ubuntu-latest | |
env: | |
IMAGE_NAME: 'forge-k8s' | |
PR_NUMBER: ${{ github.event.number == '' && inputs.pr_number || github.event.number }} | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
- name: Set variables | |
run: | | |
echo "tagged_image=${{ env.IMAGE_NAME }}:pr-${{ env.PR_NUMBER}}" >> $GITHUB_ENV | |
- name: Setup QEMU | |
uses: docker/setup-qemu-action@v3 | |
- name: Setup Docker buildx | |
uses: docker/setup-buildx-action@v3 | |
- name: Set build-args if branch is not main | |
id: set-build-args | |
run: | | |
if [ "${{ inputs.driver_k8s_branch }}" != "" ]; then | |
echo "BUILD_ARGS=KUBERNETES_DRIVER_TAG=pre-staging-${{ inputs.driver_k8s_branch }}" >> $GITHUB_ENV | |
else | |
echo "BUILD_ARGS=" >> $GITHUB_ENV | |
fi | |
- name: Build container image | |
id: build | |
uses: docker/build-push-action@v6 | |
with: | |
context: . | |
file: "./ci/Dockerfile" | |
tags: ${{ env.tagged_image }} | |
push: false | |
outputs: type=docker,dest=/tmp/k8s-forge.tar | |
build-args: ${{ env.BUILD_ARGS }} | |
env: | |
DOCKER_BUILD_SUMMARY: false | |
- name: Upload artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: k8s-forge | |
path: /tmp/k8s-forge.tar | |
retention-days: 7 | |
deploy: | |
name: Deploy application | |
if: | | |
always() && | |
needs.build.result == 'success' && | |
(needs.publish_k8s_driver.result != 'failure' || needs.upload-node-red.result != 'failure' ) && | |
(( github.event_name == 'pull_request' && github.event.action != 'closed' ) || | |
( github.event_name == 'workflow_dispatch' && github.event.action != 'closed' )) | |
runs-on: ubuntu-latest | |
environment: staging | |
needs: | |
- validate-user | |
- publish_k8s_driver | |
- upload-node-red | |
- build | |
env: | |
IMAGE_NAME: 'forge-k8s' | |
PR_NUMBER: ${{ github.event.number == '' && inputs.pr_number || github.event.number }} | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
- name: Set variables | |
run: | | |
echo "tagged_image=${{ env.IMAGE_NAME }}:pr-${{ env.PR_NUMBER}}" >> $GITHUB_ENV | |
echo "timestamp=$(date +%s)" >> $GITHUB_ENV | |
- name: Download artifact | |
uses: actions/download-artifact@v4 | |
with: | |
name: k8s-forge | |
path: /tmp | |
- name: Load image | |
run: | | |
docker load --input /tmp/k8s-forge.tar | |
docker image ls -a | |
- name: Delete artifact | |
uses: geekyeggo/delete-artifact@v5 | |
with: | |
name: k8s-forge | |
failOnError: false | |
- name: Configure AWS credentials for ECR interaction | |
id: aws-config | |
uses: aws-actions/configure-aws-credentials@v4 | |
with: | |
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
aws-secret-access-key: ${{ secrets.AWS_ACCESS_KEY_SECRET }} | |
aws-region: eu-west-1 | |
mask-aws-account-id: true | |
- name: Login to AWS ECR | |
id: login-ecr | |
uses: aws-actions/amazon-ecr-login@v2 | |
with: | |
mask-password: true | |
- name: Push to ECR | |
run: | | |
docker tag ${{ env.tagged_image }} ${{ steps.aws-config.outputs.aws-account-id }}.dkr.ecr.eu-west-1.amazonaws.com/flowforge/${{ env.tagged_image }}-${{ env.timestamp }} | |
docker push ${{ steps.aws-config.outputs.aws-account-id }}.dkr.ecr.eu-west-1.amazonaws.com/flowforge/${{ env.tagged_image }}-${{ env.timestamp }} | |
- name: Configure AWS credentials for EKS interaction | |
uses: aws-actions/configure-aws-credentials@v4 | |
with: | |
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
aws-secret-access-key: ${{ secrets.AWS_ACCESS_KEY_SECRET }} | |
aws-region: eu-west-1 | |
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/K8sAdmin | |
role-duration-seconds: 1200 | |
- name: Configure kubeconfig | |
run: | | |
aws eks update-kubeconfig --region eu-west-1 --name ${{ secrets.EKS_CLUSTER_NAME }} | |
- name: Install 1Password CLI | |
uses: 1password/install-cli-action@v1 | |
with: | |
version: 2.25.0 | |
- name: Check out FlowFuse/helm repository (to access latest helm chart) | |
uses: actions/checkout@v4 | |
with: | |
repository: 'FlowFuse/helm' | |
ref: 'main' | |
path: 'helm-repo' | |
token: ${{ secrets.GITHUB_TOKEN }} | |
- name: Check if deployment exists | |
id: check-initial-setup | |
run: | | |
if helm status --namespace "pr-${{ env.PR_NUMBER }}" flowfuse-pr-${{ env.PR_NUMBER }} &> /dev/null; then | |
echo "initialSetup=false" >> $GITHUB_ENV | |
else | |
echo "initialSetup=true" >> $GITHUB_ENV | |
fi | |
- name: Deploy | |
env: | |
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} | |
run: | | |
helm upgrade --install \ | |
--create-namespace \ | |
--namespace "pr-${{ env.PR_NUMBER }}" \ | |
--timeout 300s \ | |
--wait \ | |
--atomic \ | |
--values ci/ci-values.yaml \ | |
--set forge.image=${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.eu-west-1.amazonaws.com/flowforge/${{ env.tagged_image }}-${{ env.timestamp }} \ | |
--set forge.entryPoint=${{ env.PR_NUMBER }}.flowfuse.dev \ | |
--set forge.broker.hostname=${{ env.PR_NUMBER }}-mqtt.flowfuse.dev \ | |
--set forge.projectNamespace=pr-${{ env.PR_NUMBER }}-projects \ | |
--set forge.clusterRole.name=pr-${{ env.PR_NUMBER }}-clusterrole \ | |
--set forge.license=${{ secrets.PRE_STAGING_LICENSE }} \ | |
--set forge.aws.IAMRole=arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/flowforge_service_account_role \ | |
--set forge.email.ses.sourceArn=arn:aws:ses:eu-west-1:${{ secrets.AWS_ACCOUNT_ID }}:identity/flowfuse.com \ | |
--set forge.assistant.service.url=$(op read op://ci/staging_flowfuse/assistant_url) \ | |
--set forge.assistant.service.token=$(op read op://ci/staging_flowfuse/assistant_token) \ | |
flowfuse-pr-${{ env.PR_NUMBER }} ./helm-repo/helm/flowforge | |
- name: Initial setup | |
if: ${{ env.initialSetup == 'true' }} | |
run: | | |
./.github/scripts/initial-setup.sh ${{ env.PR_NUMBER }} ${{ secrets.INIT_CONFIG_PASSWORD_HASH }} ${{ secrets.INIT_CONFIG_ACCESS_TOKEN_HASH }} ${{ secrets.INIT_CONFIG_ACCESS_TOKEN }} ${{ secrets.INIT_CONFIG_PASSWORD }} | |
- name: Summary | |
run: | | |
echo "### :rocket: Deployment succeeded" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo "**Deployed commit SHA:** ${{ github.event.pull_request.head.sha }}" >> $GITHUB_STEP_SUMMARY | |
echo "**Deployed to:** [https://${{ env.PR_NUMBER }}.flowfuse.dev](https://${{ env.PR_NUMBER }}.flowfuse.dev)" >> $GITHUB_STEP_SUMMARY | |
create-custom-stack: | |
name: Create stack with custom Node-RED image | |
needs: [upload-node-red, deploy] | |
if: | | |
always() && | |
github.event_name == 'workflow_dispatch' && | |
needs.deploy.result == 'success' && | |
needs.upload-node-red.result == 'success' | |
runs-on: ubuntu-latest | |
environment: staging | |
env: | |
PR_NUMBER: ${{ github.event.number == '' && inputs.pr_number || github.event.number }} | |
FLOWFUSE_DOMAIN: 'flowfuse.dev' | |
steps: | |
- name: Create/update stack | |
run: | | |
customStackId=$(curl -ks -XGET -H "Authorization: Bearer ${{ secrets.INIT_CONFIG_ACCESS_TOKEN }}" https://$PR_NUMBER.$FLOWFUSE_DOMAIN/api/v1/stacks/ | jq -r '.stacks[] | select(.name == "NR-40-Custom") | .id') | |
if [ -n "$customStackId" ]; then | |
echo "Stack already exists, updating..." | |
curl -ks -X PUT \ | |
--fail-with-body \ | |
-H "Authorization: Bearer ${{ secrets.INIT_CONFIG_ACCESS_TOKEN }}" \ | |
-H "Content-Type: application/json" \ | |
-d '{"properties": {"container": "'"${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.eu-west-1.amazonaws.com/flowforge/${{ needs.upload-node-red.outputs.nr_custom_image_tag }}"'"}}' \ | |
https://$PR_NUMBER.$FLOWFUSE_DOMAIN/api/v1/stacks/$customStackId | |
else | |
echo "Stack does not exists, creating..." | |
projectTypeId=$(curl -ks -XGET -H "Authorization: Bearer ${{ secrets.INIT_CONFIG_ACCESS_TOKEN }}" https://$PR_NUMBER.$FLOWFUSE_DOMAIN/api/v1/project-types/ | jq -r '.types[].id') | |
curl -ks -w "\n" -XPOST \ | |
--fail-with-body \ | |
-H "Content-Type: application/json" \ | |
-H "Authorization: Bearer ${{ secrets.INIT_CONFIG_ACCESS_TOKEN }}" \ | |
-d '{"name":"'"NR-40-Custom"'","label":"'"4.0.x-custom"'", "projectType":"'"$projectTypeId"'","properties":{ "cpu":'"30"',"memory":'"256"',"container":"'"${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.eu-west-1.amazonaws.com/flowforge/${{ needs.upload-node-red.outputs.nr_custom_image_tag }}"'"}}' \ | |
https://$PR_NUMBER.$FLOWFUSE_DOMAIN/api/v1/stacks/ | |
fi | |
notify-slack: | |
name: Notify about pre-staging deployment | |
needs: | |
- deploy | |
- create-custom-stack | |
if: | | |
always() && | |
(needs.deploy.result != 'skipped' || needs.create-custom-stack.result != 'skipped') | |
runs-on: ubuntu-latest | |
env: | |
PR_NUMBER: ${{ github.event.number == '' && inputs.pr_number || github.event.number }} | |
steps: | |
- name: Map users | |
id: map-actor-to-slack | |
uses: icalia-actions/map-github-actor@v0.0.2 | |
with: | |
actor-map: ${{ vars.SLACK_GITHUB_USERS_MAP }} | |
default-mapping: C067BD0377F | |
- name: Post to a Slack channel | |
id: slack | |
uses: slackapi/slack-github-action@v1.27.0 | |
with: | |
channel-id: 'C067BD0377F' | |
payload: | | |
{ | |
"blocks": [ | |
{ | |
"type": "header", | |
"text": { | |
"type": "plain_text", | |
"text": "Pull Request ${{ env.PR_NUMBER }} pre-staging deployment", | |
"emoji": true | |
} | |
}, | |
{ | |
"type": "section", | |
"fields": [ | |
{ | |
"type": "mrkdwn", | |
"text": "*Status:*\n${{ needs.deploy.result == 'success' && ':white_check_mark: Success' || ':x: Failure '}}" | |
}, | |
{ | |
"type": "mrkdwn", | |
"text": ${{ toJson(env.PR_TEXT) }} | |
}, | |
{ | |
"type": "mrkdwn", | |
"text": "*Workflow run:*\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View>" | |
} | |
] | |
}, | |
{ | |
"type": "section", | |
"fields": [ | |
{ | |
"type": "mrkdwn", | |
"text": "*Author:*\n<@${{ steps.map-actor-to-slack.outputs.actor-mapping }}>" | |
}, | |
{ | |
"type": "mrkdwn", | |
"text": "*Commit SHA:*\n<${{ github.server_url }}/${{ github.repository }}/commit/${{ github.event.pull_request.head.sha }}|${{ github.event.pull_request.head.sha }}>" | |
}, | |
{ | |
"type": "mrkdwn", | |
"text": "*Deployed to:*\n<https://${{ env.PR_NUMBER }}.flowfuse.dev|https://${{ env.PR_NUMBER }}.flowfuse.dev>" | |
}, | |
{ | |
"type": "mrkdwn", | |
"text": "*Logs:*\n<https://${{ env.GRAFANA_URL }}/explore?orgId=1&left=%7B%22datasource%22:%22P8E80F9AEF21F6940%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22editorMode%22:%22code%22,%22expr%22:%22%7Bnamespace%3D%5C%22pr-${{ env.PR_NUMBER }}%5C%22%7D%20%7C%3D%20%60%60%20%7C%20json%22,%22queryType%22:%22range%22%7D%5D,%22range%22:%7B%22from%22:%22now-30m%22,%22to%22:%22now%22%7D%7D|View>" | |
} | |
] | |
} | |
] | |
} | |
env: | |
PR_TEXT: | | |
*Pull Request:* | |
<https://github.com/FlowFuse/flowfuse/pull/${{ env.PR_NUMBER }}|${{ github.event.pull_request.title }}> | |
SLACK_BOT_TOKEN: ${{ secrets.SLACK_GHBOT_TOKEN }} | |
GRAFANA_URL: ${{ secrets.STAGING_GRAFANA_URL }} | |
destroy: | |
name: Remove application | |
needs: [ validate-user ] | |
runs-on: ubuntu-latest | |
if: | | |
needs.validate-user.outputs.is_org_member == 'true' && | |
github.event_name == 'pull_request' && | |
github.event.action == 'closed' | |
environment: staging | |
env: | |
IMAGE_NAME: 'forge-k8s' | |
PR_NUMBER: ${{ github.event.number == '' && inputs.pr_number || github.event.number }} | |
steps: | |
- name: Configure AWS credentials for EKS interaction | |
uses: aws-actions/configure-aws-credentials@v4 | |
with: | |
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
aws-secret-access-key: ${{ secrets.AWS_ACCESS_KEY_SECRET }} | |
aws-region: eu-west-1 | |
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/K8sAdmin | |
role-duration-seconds: 1200 | |
- name: Configure kubeconfig | |
run: | | |
aws eks update-kubeconfig --region eu-west-1 --name ${{ secrets.EKS_CLUSTER_NAME }} | |
- name: Remove resources | |
run: | | |
if helm list -n "pr-${{ env.PR_NUMBER }}" --filter "^flowfuse-pr-${{ env.PR_NUMBER }}$" | grep "flowfuse-pr-${{ env.PR_NUMBER }}"; then | |
helm uninstall --namespace "pr-${{ env.PR_NUMBER }}" flowfuse-pr-${{ env.PR_NUMBER }} | |
sleep 15 | |
kubectl delete namespace "pr-${{ env.PR_NUMBER }}" | |
else | |
echo "Release flowfuse-pr-${{ env.PR_NUMBER }} does not exist" | |
fi |