Skip to content

Commit 54bbd5a

Browse files
committed
nats runner
1 parent cbdfceb commit 54bbd5a

8 files changed

Lines changed: 398 additions & 5 deletions

File tree

.github/workflows/nats.yaml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
name: Nats Blueprints CI and CD
2+
3+
on:
4+
push:
5+
paths:
6+
- 'nats/**'
7+
8+
pull_request:
9+
paths:
10+
- 'nats/**'
11+
12+
env:
13+
REGISTRY: ghcr.io
14+
IMAGE_NAME: code-runner
15+
BUILD_ARG_VERSION_NAME: NATS_SERVER_VERSION
16+
concurrency:
17+
group: nats-${{ github.ref }}
18+
cancel-in-progress: true
19+
20+
jobs:
21+
ci:
22+
runs-on: ubuntu-latest
23+
24+
strategy:
25+
matrix:
26+
nats_server_version: [2.10.0]
27+
28+
steps:
29+
- name: Checkout code
30+
uses: actions/checkout@v4
31+
32+
- name: Build nats:${{ matrix.nats_server_version }} image
33+
uses: ./.github/actions/docker-build
34+
with:
35+
context: ./nats
36+
dockerfile: ./nats/Dockerfile
37+
image-name: ${{ env.IMAGE_NAME }}
38+
tag: nats-server-${{ matrix.nats_server_version }}
39+
push: false
40+
container-registry: ${{ env.REGISTRY }}
41+
build-arg-version-name: ${{ env.BUILD_ARG_VERSION_NAME }}
42+
build-arg-version-value: ${{ matrix.nats_server_version }}
43+
44+
cd:
45+
runs-on: ubuntu-latest
46+
47+
if: ${{ format('refs/heads/{0}', github.event.repository.default_branch) == github.ref }}
48+
49+
strategy:
50+
matrix:
51+
nats_server_version: [2.10.0]
52+
53+
permissions:
54+
packages: write
55+
contents: read
56+
57+
needs:
58+
- ci
59+
60+
steps:
61+
- name: Checkout code
62+
uses: actions/checkout@v4
63+
64+
- name: Build and push nats:${{ matrix.nats_server_version }} image
65+
uses: ./.github/actions/docker-build
66+
with:
67+
context: ./nats
68+
dockerfile: ./nats/Dockerfile
69+
image-name: ${{ env.IMAGE_NAME }}
70+
tag: nats-server-${{ matrix.nats_server_version }}
71+
push: true
72+
container-registry: ${{ env.REGISTRY }}
73+
container-registry-username: ${{ github.actor }}
74+
container-registry-password: ${{ secrets.GITHUB_TOKEN }}
75+
build-arg-version-name: ${{ env.BUILD_ARG_VERSION_NAME }}
76+
build-arg-version-value: ${{ matrix.nats_server_version }}

nats/Dockerfile

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Accept Nats server version as build argument
2+
ARG NATS_SERVER_VERSION=2.10.0
3+
4+
# ------------------------------------------
5+
FROM nats:${NATS_SERVER_VERSION}-alpine AS base
6+
7+
# ------------------------------------------
8+
FROM base AS builder
9+
10+
COPY install.sh /tmp/install.sh
11+
12+
RUN chmod +x /tmp/install.sh && /tmp/install.sh
13+
14+
# ------------------------------------------
15+
FROM base AS runner
16+
17+
# Install tools
18+
RUN apk add --no-cache bash coreutils busybox-extras
19+
20+
# Create data directory
21+
RUN mkdir -p /data
22+
23+
# Copy nats binary from builder image
24+
COPY --from=builder /usr/local/bin/nats /usr/local/bin/nats
25+
26+
# Copy nats.conf from builder image
27+
COPY nats.conf /etc/nats/nats-server.conf
28+
29+
# Set working directory
30+
WORKDIR /app
31+
32+
COPY run.sh /run.sh
33+
RUN chmod +x /run.sh
34+
35+
ENTRYPOINT ["/run.sh"]

nats/install.sh

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/bin/sh
2+
3+
# Step 1: Detect OS and Architecture
4+
OS=$(uname | tr '[:upper:]' '[:lower:]')
5+
ARCH=$(uname -m)
6+
7+
# Normalize architecture name
8+
case "$ARCH" in
9+
x86_64) ARCH="amd64" ;;
10+
amd64) ARCH="amd64" ;;
11+
aarch64) ARCH="arm64" ;;
12+
armv7l) ARCH="arm7" ;;
13+
armv6l) ARCH="arm6" ;;
14+
i386 | i686) ARCH="386" ;;
15+
*) echo "Unsupported arch: $ARCH"; exit 1;;
16+
esac
17+
18+
# Step 2: Get Latest NATS CLI Version
19+
VERSION=$(wget --no-check-certificate -qO- https://api.github.com/repos/nats-io/natscli/releases/latest | grep '"tag_name":' | head -n 1 | cut -d '"' -f4)
20+
21+
if [ -z "$VERSION" ]; then
22+
echo "Failed to get latest NATS version."
23+
exit 1
24+
fi
25+
26+
echo "Detected OS: $OS"
27+
echo "Detected Architecture: $ARCH"
28+
echo "Latest NATS Version: $VERSION"
29+
30+
# Step 3: Build Download URL
31+
VERSION_NUMBER=$(echo "$VERSION" | sed 's/^v//')
32+
FILE_NAME="nats-${VERSION_NUMBER}-${OS}-${ARCH}"
33+
ZIP_FILE="${FILE_NAME}.zip"
34+
URL="https://github.com/nats-io/natscli/releases/download/${VERSION}/${ZIP_FILE}"
35+
36+
# Step 4: Download and Extract
37+
wget --no-check-certificate $URL -O /tmp/$ZIP_FILE
38+
39+
if [ $? -ne 0 ]; then
40+
echo "Failed to download NATS."
41+
exit 1
42+
fi
43+
44+
cd /tmp
45+
unzip $ZIP_FILE
46+
47+
# Step 5: Move binary to /usr/local/bin
48+
mv $FILE_NAME/nats /usr/local/bin/
49+
50+
# Step 6: Cleanup
51+
rm -rf $ZIP_FILE $FILE_NAME
52+
53+
echo "NATS server installed successfully."

nats/nats.conf

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Client port of 4222 on all interfaces
2+
port: 4222
3+
4+
# Monitoring and metrics
5+
server_name: "nats-server"
6+
monitor_port: 8222
7+
8+
# Enable JetStream
9+
jetstream {
10+
store_dir: "/data/jetstream"
11+
max_mem: 5MB
12+
max_file: 2MB
13+
}
14+
15+
# Logging configuration
16+
logtime: true
17+
log_file: "/data/nats.log"
18+
debug: false
19+
trace: false
20+
21+
# Connection limits and timeouts
22+
max_connections: 5
23+
max_subscriptions: 0
24+
max_payload: 1MB
25+
max_pending: 2MB
26+
write_deadline: 5s
27+
max_control_line: 4096
28+
29+
30+
# Account Structure
31+
# --------------------------------------------------
32+
# 1. SYS Account (System Account)
33+
# User: sys / syspass
34+
# Purpose: System operations and monitoring
35+
# Permissions: Can only access $SYS.> subjects
36+
# Exports: System services for other accounts
37+
38+
# 3. Authorization Section
39+
# User: ruser / T0pS3cr3t
40+
# Purpose: Cluster route authentication
41+
# Permissions: Full access for cluster routing
42+
43+
# Define accounts: SYS for system operations and CLUSTER for cluster communication
44+
accounts: {
45+
SYS: {
46+
users: [
47+
{
48+
user: sys
49+
password: syspass
50+
permissions: {
51+
publish: {
52+
allow: ["$SYS.>", "_INBOX.>"]
53+
}
54+
subscribe: {
55+
allow: ["$SYS.>", "_INBOX.>"]
56+
}
57+
}
58+
}
59+
]
60+
exports: [
61+
{ service: "$SYS.>" }
62+
]
63+
},
64+
}
65+
66+
# System account config
67+
system_account: SYS
68+
69+
authorization: {
70+
users: [
71+
{
72+
user: "ruser"
73+
password: "T0pS3cr3t"
74+
permissions: {
75+
publish: ">"
76+
subscribe: ">"
77+
}
78+
}
79+
]
80+
}

nats/readme.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
This Dockerfile will creates a docker image which can be used to run `nats` commands.
2+
3+
# How to build
4+
5+
```
6+
# building with nats-server 2.10.0
7+
docker build --build-arg NATS_SERVER_VERSION=2.10.0 -t code-runner:nats-2.10.0 .
8+
```
9+
10+
The Nodejs version can be specified by passing the `NATS_SERVER_VERSION` argument to the `docker build` command.
11+
12+
# How to use
13+
14+
1. Run code with default timeout
15+
16+
```sh
17+
docker run --rm code-runner:nats-2.10.0 'nats --user=mahdi account info'
18+
```
19+
20+
2. Run code with custom timeout
21+
22+
```sh
23+
docker run --rm code-runner:nats-2.10.0 --timeout 3 'nats --user=mahdi account info'
24+
```
25+
26+
3. Multiline + custom timeout
27+
28+
```sh
29+
docker run --rm -i code-runner:nats-2.10.0 --timeout 5 <<EOF
30+
nats account info && \
31+
nats stream ls
32+
EOF
33+
```

nats/run.sh

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#!/bin/bash
2+
3+
start_nats_server() {
4+
# Run nats-server and purge its logs
5+
nats-server --config /etc/nats/nats-server.conf > /dev/null 2>&1 &
6+
7+
# Wait for nats-server to be ready
8+
timeout_seconds=10
9+
start_time=$(date +%s.%N)
10+
deadline_time=$(echo "$start_time + $timeout_seconds" | bc -l)
11+
while ! timeout 1 nats --user=sys --password=syspass server ping >/dev/null 2>&1; do
12+
sleep 0.2
13+
current_time=$(date +%s.%N)
14+
if (( $(echo "$current_time > $deadline_time" | bc -l) )); then
15+
echo "⏰ Nats server failed to start after ${timeout_seconds} seconds."
16+
exit 1
17+
fi
18+
done
19+
}
20+
21+
stop_nats_server() {
22+
# kill nats-server
23+
kill -9 $(pgrep nats-server)
24+
25+
# wait for nats-server to exit
26+
wait $(pgrep nats-server)
27+
}
28+
29+
parse_args() {
30+
TIMEOUT=10
31+
while [[ "$#" -gt 0 ]]; do
32+
case $1 in
33+
--timeout)
34+
TIMEOUT="$2"
35+
shift 2
36+
;;
37+
*)
38+
COMMAND_ARGS=("$@")
39+
break
40+
;;
41+
esac
42+
done
43+
}
44+
45+
read_command_from_stdin() {
46+
if [ ${#COMMAND_ARGS[@]} -eq 0 ] && [ ! -t 0 ]; then
47+
COMMAND=$(cat -)
48+
COMMAND_ARGS=($COMMAND)
49+
fi
50+
}
51+
52+
preprocess_command() {
53+
if [ ${#COMMAND_ARGS[@]} -eq 0 ]; then
54+
echo "No command provided."
55+
exit 1
56+
fi
57+
if [ ${#COMMAND_ARGS[@]} -eq 1 ] && [[ "${COMMAND_ARGS[0]}" == *" "* ]]; then
58+
COMMAND_ARGS=(${COMMAND_ARGS[0]})
59+
fi
60+
COMMAND_STR="${COMMAND_ARGS[*]}"
61+
62+
# Handle line continuations
63+
COMMAND_STR=$(echo "$COMMAND_STR" | sed 's/\\[[:space:]]/ /g')
64+
COMMAND_STR=$(echo "$COMMAND_STR" | sed 's/\\$//g')
65+
}
66+
67+
add_auth_flags() {
68+
# Replace both && and ; with a single delimiter for splitting
69+
# IFS cannot handle multi-character sequences like &&, so we use sed to replace them
70+
SPLIT_CLEANED_COMMAND=$(echo "$COMMAND_STR" | sed 's/&&/|/g; s/;/|/g')
71+
72+
IFS='|' read -ra SUBCOMMANDS <<< "$SPLIT_CLEANED_COMMAND"
73+
AUTH_COMMANDS=()
74+
for subcommand in "${SUBCOMMANDS[@]}"; do
75+
subcommand=$(echo "$subcommand" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
76+
if [[ -z "$subcommand" ]]; then
77+
continue
78+
fi
79+
if [[ "$subcommand" =~ ^nats ]]; then
80+
# Remove any existing auth flags
81+
subcommand=$(echo "$subcommand" | sed -E 's/--user(=[^ ]+| [^ ]+)? ?//g; s/--password(=[^ ]+| [^ ]+)? ?//g')
82+
83+
# Add auth flags
84+
auth_subcommand=$(echo "$subcommand" | sed 's/^nats/& --user=ruser --password=T0pS3cr3t/')
85+
AUTH_COMMANDS+=("$auth_subcommand")
86+
else
87+
AUTH_COMMANDS+=("$subcommand")
88+
fi
89+
done
90+
PROCESSED_COMMAND=$(printf '%s && ' "${AUTH_COMMANDS[@]}" | sed 's/ && $//')
91+
}
92+
93+
run_commands() {
94+
start_nats_server
95+
timeout "$TIMEOUT" bash -c "$PROCESSED_COMMAND"
96+
if [ $? -eq 124 ]; then
97+
stop_nats_server
98+
echo "⏰ Execution timed out after ${TIMEOUT} seconds."
99+
exit 124
100+
fi
101+
}
102+
103+
main() {
104+
parse_args "$@"
105+
read_command_from_stdin
106+
preprocess_command
107+
add_auth_flags
108+
run_commands
109+
}
110+
111+
main "$@"

0 commit comments

Comments
 (0)