Skip to content

Commit

Permalink
Merge pull request #2344 from artilleryio/ci-worker-image
Browse files Browse the repository at this point in the history
feat: add Docker image for ECS/Fargate workers + build workflow
  • Loading branch information
hassy committed Nov 30, 2023
2 parents e8a69c3 + 68d4e49 commit a2602d2
Show file tree
Hide file tree
Showing 8 changed files with 783 additions and 19 deletions.
11 changes: 0 additions & 11 deletions .dockerignore

This file was deleted.

55 changes: 55 additions & 0 deletions .github/workflows/docker-ecs-worker-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Build & publish ECS/Fargate worker image to ECR

on:
push:
branches:
- main

permissions:
id-token: write
contents: read

jobs:
build_docker_image:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Show git ref
run: |
echo ${{ github.ref }}
echo ${{ github.event.pull_request.head.sha }}
echo ${{ github.sha }}
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-region: us-east-1
audience: sts.amazonaws.com
role-to-assume: ${{ secrets.ECR_WORKER_IMAGE_PUSH_ROLE_ARN }}
role-session-name: OIDCSession
mask-aws-account-id: true

- name: Login to Amazon ECR Public
id: login-ecr-public
uses: aws-actions/amazon-ecr-login@v1
with:
registry-type: public

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: public.ecr.aws/d8a4z9o5/artillery-worker
tags: |
type=semver,pattern={{version}}
- name: Build the Docker image
run: |
docker build . --build-arg="WORKER_VERSION=${{ github.sha }}" --tag public.ecr.aws/d8a4z9o5/artillery-worker:${{ github.sha }} -f ./packages/artillery/lib/platform/aws-ecs/worker/Dockerfile
- name: Push Docker image
run: |
docker push public.ecr.aws/d8a4z9o5/artillery-worker:${{ github.sha }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

module.exports.Plugin = ArtilleryInspectScriptPlugin;

const { btoa } = require('../../util');

function ArtilleryInspectScriptPlugin(script, events) {
this.script = script;
this.events = events;

const checksConfig = script.config?.ensure || script.config?.plugins?.ensure;

if (checksConfig) {
console.log(
'inspect-script.config.ensure=' + btoa(JSON.stringify(checksConfig))
);
}

return this;
}
ArtilleryInspectScriptPlugin.prototype.cleanup = function (done) {
done(null);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const AWS = require('aws-sdk');
const debug = require('debug')('plugin:sqsReporter');
const uuid = require('node:crypto').randomUUID;

module.exports = {
Plugin: ArtillerySQSPlugin,
LEGACY_METRICS_FORMAT: false
};

function ArtillerySQSPlugin(script, events) {
this.script = script;
this.events = events;

this.unsent = 0;

const self = this;

// List of objects: [{key: 'SomeKey', value: 'SomeValue'}, ...]
this.tags = process.env.SQS_TAGS ? JSON.parse(process.env.SQS_TAGS) : [];
this.testId = null;
let messageAttributes = {};

this.tags.forEach(function (tag) {
if (tag.key === 'testId') {
self.testId = tag.value;
}
messageAttributes[tag.key] = {
DataType: 'String',
StringValue: tag.value
};
});

this.messageAttributes = messageAttributes;

this.sqs = new AWS.SQS({
region:
process.env.SQS_REGION || script.config.plugins['sqs-reporter'].region
});

this.queueUrl =
process.env.SQS_QUEUE_URL || script.config.plugins['sqs-reporter'].queueUrl;

events.on('stats', (statsOriginal) => {
let body;
const serialized = global.artillery.__SSMS.serializeMetrics(statsOriginal);
body = {
event: 'workerStats',
stats: serialized
};
body = JSON.stringify(body);

debug('Prepared messsage body');
debug(body);

this.unsent++;

// TODO: Check that body is not longer than 255kb
const params = {
MessageBody: body,
QueueUrl: this.queueUrl,
MessageAttributes: this.messageAttributes,
MessageDeduplicationId: uuid(),
MessageGroupId: this.testId
};

this.sqs.sendMessage(params, (err, data) => {
if (err) {
console.error(err);
}
this.unsent--;
});
});

events.on('done', (_stats) => {
this.unsent++;
const body = JSON.stringify({
event: 'done'
});

const params = {
MessageBody: body,
QueueUrl: this.queueUrl,
MessageAttributes: this.messageAttributes,
MessageDeduplicationId: uuid(),
MessageGroupId: this.testId
};

this.sqs.sendMessage(params, (err, data) => {
if (err) {
console.error(err);
}

this.unsent--;
});
});

return this;
}
ArtillerySQSPlugin.prototype.cleanup = function (done) {
const interval = setInterval(() => {
if (this.unsent <= 0) {
clearInterval(interval);
done(null);
}
}, 200).unref();
};
25 changes: 17 additions & 8 deletions packages/artillery/lib/platform/aws-ecs/legacy/run-cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -1051,13 +1051,22 @@ async function ensureTaskExists(context) {
process.env.WORKER_IMAGE_URL ||
`301676560329.dkr.ecr.${context.region}.amazonaws.com/artillery-pro/aws-ecs-node:v2-${IMAGE_VERSION}`;

// ['NPM_TOKEN', 'NPM_REGISTRY', 'NPM_SCOPE', 'NPM_SCOPE_REGISTRY', 'NPMRC', 'ARTIFACTORY_AUTH', 'ARTIFACTORY_EMAIL']
const secrets = [].concat(context.extraSecrets).map((secretName) => {
return {
name: secretName,
valueFrom: `arn:aws:ssm:${context.backendRegion}:${context.accountId}:parameter/artilleryio/${secretName}`
};
});
const secrets = [
'NPM_TOKEN',
'NPM_REGISTRY',
'NPM_SCOPE',
'NPM_SCOPE_REGISTRY',
'NPMRC',
'ARTIFACTORY_AUTH',
'ARTIFACTORY_EMAIL'
]
.concat(context.extraSecrets)
.map((secretName) => {
return {
name: secretName,
valueFrom: `arn:aws:ssm:${context.region}:${context.accountId}:parameter/artilleryio/${secretName}`
};
});

let taskDefinition = {
family: context.taskName,
Expand Down Expand Up @@ -1322,7 +1331,7 @@ async function generateTaskOverrides(context) {
'-a',
util.btoa(JSON.stringify(cliArgs)),
'-r',
context.region || context.backendRegion,
context.region,
'-q',
process.env.SQS_QUEUE_URL || context.sqsQueueUrl,
'-i',
Expand Down
35 changes: 35 additions & 0 deletions packages/artillery/lib/platform/aws-ecs/worker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# ********************************
# NOTE: Version we use here needs to be kept consistent with that in
# artillery-engine-playwright.
# ********************************
FROM mcr.microsoft.com/playwright:v1.39.0
LABEL maintainer="team@artillery.io"

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && \
apt-get install -y bash jq pwgen python2-minimal make g++ curl git zip tree awscli

ARG WORKER_VERSION
ENV WORKER_VERSION=$WORKER_VERSION

WORKDIR /artillery
COPY LICENSE.txt LICENSE.txt
COPY package.json package.json
COPY packages packages

RUN cd /artillery && \
npm install -w artillery --ignore-scripts --omit=dev && \
npm cache clean --force && \
rm -rf /root/.cache && \
ln -s /artillery/node_modules/.bin/artillery /usr/local/bin/artillery && \
rm -rf /ms-playwright/firefox* && \
rm -rf /ms-playwright/webkit* && \
echo "ok"

COPY ./packages/artillery/lib/platform/aws-ecs/worker/loadgen-worker /artillery/loadgen-worker
COPY ./packages/artillery/lib/platform/aws-ecs/worker/helpers.sh /artillery/helpers.sh

RUN chmod +x /artillery/loadgen-worker

ENTRYPOINT ["/artillery/loadgen-worker"]
73 changes: 73 additions & 0 deletions packages/artillery/lib/platform/aws-ecs/worker/helpers.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env bash

generate_npmrc () {
if [[ "$ARTIFACTORY_AUTH" != "null" ]] && [[ "$ARTIFACTORY_EMAIL" != "null" ]] ; then
echo "_auth=$ARTIFACTORY_AUTH"
echo "email=$ARTIFACTORY_EMAIL"
echo "always-auth=true"
else
# If only one is set - print a diagnostic message;
# otherwise neither is set so do nothing.
if [[ "$ARTIFACTORY_AUTH" != "null" ]] || [[ "$ARTIFACTORY_EMAIL" != "null" ]] ; then
echo "Both ARTIFACTORY_AUTH and ARTIFACTORY_EMAIL must be set for Artifactory auth to work"
fi
fi

# NOTE: Default value for NPM_SCOPE and NPM_TOKEN is "null"
if [[ "$NPM_REGISTRY" == "null" ]] ; then
NPM_REGISTRY="https://registry.npmjs.org/"
fi

NPM_REGISTRY_BASE="${NPM_REGISTRY#http:}"
NPM_REGISTRY_BASE="${NPM_REGISTRY#https:}"

NPM_TOKEN="${NPM_TOKEN:-"null"}"
NPM_SCOPE="${NPM_SCOPE:-"null"}"

# Set registry URL:
# npm config set registry "$NPM_REGISTRY"
echo "registry=${NPM_REGISTRY}"

if [[ ! "$NPM_TOKEN" = "null" ]] ; then
echo "${NPM_REGISTRY_BASE}:_authToken=${NPM_TOKEN}"
fi

if [[ "$NPM_SCOPE" != "null" ]] ; then
if [[ "$NPM_SCOPE_REGISTRY" != "null" ]] ; then
echo "${NPM_SCOPE}:registry=${NPM_SCOPE_REGISTRY}"
else
# npm config set "${NPM_SCOPE}:registry" "$NPM_REGISTRY"
echo "${NPM_SCOPE}:registry=${NPM_REGISTRY}"
fi
fi

# Any extra bits from the user:
if [[ "$NPMRC" != "null" ]] ; then
echo "$NPMRC"
fi
}

base64d () {
# Not expecting any tabs or newlines in the input so not read'ing in a loop.
read encoded
if [[ "$(uname)" == "Darwin" ]] ; then
printf "%s" "$encoded" | base64 -D
elif [[ "$(uname)" == "Linux" ]] ; then
printf "%s" "$encoded" | base64 -d
else
printf "Unknown platform: $(uname)"
# shellcheck disable=SC2034
EXIT_CODE=$ERR_UNKNOWN_PLATFORM
exit
fi
}

debug () {
if [[ -n $DEBUG ]] ; then
printf -- "%s\\n" "$@"
fi
}

progress () {
printf "******** [$worker_id] %s\\n" "$1"
}
Loading

0 comments on commit a2602d2

Please sign in to comment.