Skip to content

Refactor: Convert Docker action to composite action for speed and add… #15

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

Closed
wants to merge 3 commits into from
Closed
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
199 changes: 199 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
name: Test Composite Action

on: [push, pull_request]

jobs:
test-action:
runs-on: ubuntu-latest
services:
sshd:
image: rastasheep/ubuntu-sshd:jammy # Updated to jammy for Ubuntu 22.04
ports:
- 2222:22
# We will configure the sshd service using a step below

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Generate SSH keys & Configure Service Container
id: ssh_setup # Give an id to this step to reference its outputs
run: |
# Generate SSH keys
ssh-keygen -t rsa -b 2048 -f test_ssh_key -N ""
sudo apt-get update && sudo apt-get install -y sshpass netcat-openbsd

echo "Waiting for SSH service (localhost:2222) to be ready..."
MAX_WAIT_ATTEMPTS=12 # Wait for up to 60 seconds (12 * 5s)
CURRENT_WAIT_ATTEMPT=0
until nc -zv localhost 2222; do
CURRENT_WAIT_ATTEMPT=$((CURRENT_WAIT_ATTEMPT+1))
if [ $CURRENT_WAIT_ATTEMPT -gt $MAX_WAIT_ATTEMPTS ]; then
echo "Service sshd on port 2222 did not become available."
exit 1
fi
echo "Waiting for port 2222... attempt $CURRENT_WAIT_ATTEMPT"
sleep 5
done
echo "SSH service port is open."

echo "Ensuring /root/.ssh directory exists on service container..."
sshpass -p root ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 root@localhost "mkdir -p /root/.ssh && chmod 700 /root/.ssh" || { echo "Failed to create /root/.ssh on service container"; exit 1; }

echo "Attempting to copy SSH public key to service container..."
MAX_SCP_ATTEMPTS=5
COUNT=0
while [ $COUNT -lt $MAX_SCP_ATTEMPTS ]; do
sshpass -p root scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -P 2222 ./test_ssh_key.pub root@localhost:/root/.ssh/authorized_keys && break
COUNT=$((COUNT+1))
echo "SSH key copy attempt $COUNT failed. Retrying in 5s..."
sleep 5
done
if [ $COUNT -eq $MAX_SCP_ATTEMPTS ]; then
echo "Failed to copy SSH key to service container after $MAX_SCP_ATTEMPTS attempts."
# For debugging, show if the .ssh directory exists
sshpass -p root ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 root@localhost "ls -la /root/ && ls -la /root/.ssh/" || echo "Failed to list /root/.ssh on service."
exit 1
fi
echo "Copied generated public key to service container's /root/.ssh/authorized_keys."

# Output keys for other steps
# Need to escape multi-line private key for ::set-output (deprecated) or GITHUB_OUTPUT
# Using GITHUB_OUTPUT format
echo "private_key<<EOF" >> $GITHUB_OUTPUT
cat test_ssh_key >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "public_key=$(cat test_ssh_key.pub)" >> $GITHUB_OUTPUT
echo "Generated and set SSH key outputs for use in subsequent steps."

- name: Setup Mock Docker Command
run: |
echo '#!/bin/bash' > ./docker_mock.sh
echo 'echo "DOCKER_MOCK_CALLED_WITH: $@" >> /tmp/docker_calls.log' >> ./docker_mock.sh
echo 'if [[ "$1" == "context" && "$2" == "create" ]]; then echo "Context created"; exit 0; fi' >> ./docker_mock.sh
echo 'if [[ "$1" == "context" && "$2" == "use" ]]; then echo "Context used"; exit 0; fi' >> ./docker_mock.sh
echo 'if [[ "$1" == "ps" ]]; then echo "CONTAINER ID IMAGE COMMAND"; exit 0; fi' >> ./docker_mock.sh
echo 'if [[ "$1" == "login" ]]; then echo "Login Succeeded"; exit 0; fi' >> ./docker_mock.sh
echo 'if [[ "$1" == "compose" && "$3" == "pull" ]]; then echo "Pulling done"; exit 0; fi' >> ./docker_mock.sh
echo 'if [[ "$1" == "compose" ]]; then echo "Compose command executed"; exit 0; fi' >> ./docker_mock.sh
echo 'if [[ "$1" == "stack" && "$2" == "deploy" ]]; then echo "Stack deploy command executed"; exit 0; fi' >> ./docker_mock.sh
echo 'exit 0' >> ./docker_mock.sh
chmod +x ./docker_mock.sh
sudo ln -sf "$PWD/docker_mock.sh" /usr/local/bin/docker
echo "Mock Docker command setup at /usr/local/bin/docker. Calls logged to /tmp/docker_calls.log"
# Ensure log file is clean before first test
rm -f /tmp/docker_calls.log

# --- Test Case 1: Basic SSH and Docker Context (Compose ps) ---
- name: Test 1 - Basic SSH, Docker Context, Compose
uses: ./ # Uses the composite action in the root of the repository
with:
remote_docker_host: root@localhost
ssh_port: 2222
ssh_private_key: ${{ steps.ssh_setup.outputs.private_key }}
ssh_public_key: ${{ steps.ssh_setup.outputs.public_key }}
args: "ps" # For docker compose ps

- name: Verify Test 1
shell: bash
run: |
echo "Verifying Test 1..."
if [ ! -f /tmp/docker_calls.log ]; then echo "Docker log not found!"; exit 1; fi
cat /tmp/docker_calls.log
grep -q "DOCKER_MOCK_CALLED_WITH: context create remote --docker host=ssh://root@localhost:2222" /tmp/docker_calls.log || exit 1
grep -q "DOCKER_MOCK_CALLED_WITH: context use remote" /tmp/docker_calls.log || exit 1
grep -q "DOCKER_MOCK_CALLED_WITH: ps" /tmp/docker_calls.log || exit 1 # From action's context setup test
grep -q "DOCKER_MOCK_CALLED_WITH: compose -f docker-compose.yml pull" /tmp/docker_calls.log || exit 1
grep -q "DOCKER_MOCK_CALLED_WITH: compose -f docker-compose.yml ps" /tmp/docker_calls.log || exit 1
echo "Test 1 Verification Successful"
rm -f /tmp/docker_calls.log

# --- Test Case 2: Upload Directory ---
- name: Test 2 - Upload Directory
uses: ./
with:
remote_docker_host: root@localhost
ssh_port: 2222
ssh_private_key: ${{ steps.ssh_setup.outputs.private_key }}
ssh_public_key: ${{ steps.ssh_setup.outputs.public_key }}
upload_directory: 'true'
docker_compose_directory: '.github' # Upload this directory for testing
args: "ps" # Dummy args for compose
post_upload_command: "mkdir -p /tmp && echo 'post_upload_executed' > /tmp/post_upload.txt"

- name: Verify Test 2
shell: bash
run: |
echo "Verifying Test 2..."
sshpass -p root ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 root@localhost "ls -d /root/.github/workflows" || (echo "Uploaded directory not found" && exit 1)
sshpass -p root ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 root@localhost "cat /tmp/post_upload.txt | grep 'post_upload_executed'" || (echo "Post upload command verification failed" && exit 1)
echo "Test 2 Verification Successful"
sshpass -p root ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 root@localhost "rm -rf /root/.github /tmp/post_upload.txt"
rm -f /tmp/docker_calls.log

# --- Test Case 3: Docker Swarm Mode ---
- name: Test 3 - Docker Swarm Mode
uses: ./
with:
remote_docker_host: root@localhost
ssh_port: 2222
ssh_private_key: ${{ steps.ssh_setup.outputs.private_key }}
ssh_public_key: ${{ steps.ssh_setup.outputs.public_key }}
docker_swarm: 'true'
args: "deploy --prune myapp" # Example swarm args
compose_file_path: "docker-stack.yml"

- name: Verify Test 3
shell: bash
run: |
echo "Verifying Test 3..."
if [ ! -f /tmp/docker_calls.log ]; then echo "Docker log not found!"; exit 1; fi
cat /tmp/docker_calls.log
grep -q "DOCKER_MOCK_CALLED_WITH: deploy --prune myapp stack deploy --compose-file docker-stack.yml" /tmp/docker_calls.log || exit 1
echo "Test 3 Verification Successful"
rm -f /tmp/docker_calls.log

# --- Test Case 4: Docker Login ---
- name: Test 4 - Docker Login
uses: ./
with:
remote_docker_host: root@localhost
ssh_port: 2222
ssh_private_key: ${{ steps.ssh_setup.outputs.private_key }}
ssh_public_key: ${{ steps.ssh_setup.outputs.public_key }}
args: "ps" # Dummy args
docker_login_user: "testuser"
docker_login_password: "testpassword"
docker_login_registry: "fakeregistry.com"

- name: Verify Test 4
shell: bash
run: |
echo "Verifying Test 4..."
if [ ! -f /tmp/docker_calls.log ]; then echo "Docker log not found!"; exit 1; fi
cat /tmp/docker_calls.log
grep -q "DOCKER_MOCK_CALLED_WITH: login -u testuser --password-stdin fakeregistry.com" /tmp/docker_calls.log || exit 1
echo "Test 4 Verification Successful"
rm -f /tmp/docker_calls.log

# --- Test Case 5: Tailscale SSH (mocked - verifies action proceeds without explicit key inputs) ---
- name: Test 5 - Tailscale SSH
uses: ./
with:
remote_docker_host: root@localhost # Still need for Docker context setup
ssh_port: 2222
tailscale_ssh: 'true'
args: "ps"
# ssh_private_key and ssh_public_key are intentionally omitted for this test case

- name: Verify Test 5
shell: bash
run: |
echo "Verifying Test 5 (Tailscale SSH mode)..."
if [ ! -f /tmp/docker_calls.log ]; then echo "Docker log not found!"; exit 1; fi
cat /tmp/docker_calls.log
# Essential Docker commands should still be logged by the mock
grep -q "DOCKER_MOCK_CALLED_WITH: context create remote --docker host=ssh://root@localhost:2222" /tmp/docker_calls.log || exit 1
grep -q "DOCKER_MOCK_CALLED_WITH: compose -f docker-compose.yml ps" /tmp/docker_calls.log || exit 1
echo "Test 5 Verification Successful"
rm -f /tmp/docker_calls.log
5 changes: 0 additions & 5 deletions Dockerfile

This file was deleted.

76 changes: 48 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,70 @@
# Docker Compose Gitops
# Docker Compose Gitops Action
A [GitHub Action](https://github.com/marketplace/actions/docker-compose-gitops) making GitOps with the simplicity of docker-compose possible, using SSH or optionally Tailscale SSH, with support for docker swarm, uploading directory for bind mounts and other features!

The Action is adapted from work by [TapTap21](https://github.com/TapTap21/docker-remote-deployment-action) and [wshihadeh](https://github.com/marketplace/actions/docker-deployment)
**Note:** This action has been refactored into a [composite action](https://docs.github.com/en/actions/creating-actions/creating-a-composite-action). This change aims to speed up workflow execution by eliminating the need to pull a Docker image for the action itself, running steps directly on the runner. The functionality and inputs remain the same.

The Action is adapted from work by [TapTap21](https://github.com/TapTap21/docker-remote-deployment-action) and [wshihadeh](https://github.com/marketplace/actions/docker-deployment).

## Example

Here is an example of how to use the action
Here is an example of how to use the action. Usage remains the same as before:

```yaml
- name: Tailscale
uses: tailscale/github-action@ce41a99162202a647a4b24c30c558a567b926709
uses: tailscale/github-action@v2 # Consider using a more specific version/commit SHA
with:
authkey: ${{ secrets.TAILSCALE_AUTHKEY }}
hostname: Github-actions
hostname: Github-actions # Optional: set a hostname for the Tailscale node

- name: Start Deployment
uses: FarisZR/docker-compose-gitops-action@v1
uses: FarisZR/docker-compose-gitops-action@v1 # Or your current version
with:
remote_docker_host: root@100.107.201.124
tailscale_ssh: true # no need for manual private and public keys
remote_docker_host: root@your_tailscale_ip_or_hostname # e.g., root@100.x.x.x or your custom hostname
tailscale_ssh: true # Set to true if using Tailscale for SSH
compose_file_path: postgres/docker-compose.yml
upload_directory: true # upload docker directory
docker_compose_directory: postgres # directory to upload
docker_login_password: ${{ secrets.DOCKER_REPO_PASSWORD }}
docker_login_user: ${{ secrets.DOCKER_REPO_USERNAME }}
docker_login_registry : ${{ steps.login-ecr.outputs.registry }}
args: -p postgres up -d
upload_directory: true
docker_compose_directory: postgres
# Example Docker login (optional)
# docker_login_user: ${{ secrets.DOCKER_HUB_USER }}
# docker_login_password: ${{ secrets.DOCKER_HUB_PASSWORD }}
# docker_login_registry: docker.io # Optional, defaults to Docker Hub
args: -p postgres up -d --remove-orphans
```

## Action Inputs

- `args` - Docker compose/stack command arguments. Example: `-p app_stack_name -d up` required
- `remote_docker_host` Specify Remote Docker host. The input value must be in the following format (user@host) required
- `tailscale_ssh` Enables Tailscale ssh mode, which uses managed ssh keys from Tailscale, and skips the private and public keys. default: false
- `ssh_public_key` Remote Docker SSH public key. Required when Tailscale ssh isn't enabled
- `ssh_private_key` SSH private key used in PEM format to connect to the docker host. Required when Tailscale ssh isn't enabled
- `ssh_port` The SSH port to be used. Default is 22.
- `compose_file_path` Docker compose file path. Default is `docker-compose.yml`(repo root), sub-directory Example: `caddy/docker-compose.yml`
- `upload_directory` Uploads docker compose directory, useful for extra files like Configs. Optional
- `docker_compose_directory` Specifies which directory in the repository to upload, needed for upload_directory
- `post_upload_command` Optional input to execute a command post upload, when `upload_directory` is enabled. Useful for things like changing permissions before starting containers.
- `docker_swarm` Uses docker swarm instead of compose by using the docker stack command, default: false
- `docker_login_user` The username for the container repository user. (DockerHub, ECR, etc.). Optional.
- `docker_login_password` The password for the container repository user.
- `docker_login_registry` The docker container registry to authenticate against Optional
The inputs remain unchanged:

- `args` - Docker compose/stack command arguments. Example: `-p app_stack_name -d up` (required)
- `remote_docker_host` - Specify Remote Docker host. The input value must be in the following format `user@host` (required)
- `tailscale_ssh` - Enables Tailscale SSH mode, which leverages Tailscale's managed SSH connections. If `true`, the `ssh_public_key` and `ssh_private_key` inputs are not required by this action (though your Tailscale setup handles authentication). Default: `false`
- `ssh_public_key` - Remote Docker SSH public key. Required when `tailscale_ssh` is `false`.
- `ssh_private_key` - SSH private key used in PEM format to connect to the docker host. Required when `tailscale_ssh` is `false`.
- `ssh_port` - The SSH port to be used. Default is `22`.
- `compose_file_path` - Docker compose file path. Default is `docker-compose.yml` (in the repo root). Example for a sub-directory: `caddy/docker-compose.yml`
- `upload_directory` - If `true`, uploads the `docker_compose_directory`. Useful for configuration files needed alongside your containers. Default: `false` (Optional)
- `docker_compose_directory` - Specifies which directory in the repository to upload. Required if `upload_directory` is `true`.
- `post_upload_command` - Optional command to execute on the remote host after a successful upload (if `upload_directory` is `true`). Useful for tasks like setting file permissions.
- `docker_swarm` - If `true`, uses `docker stack deploy` for Docker Swarm mode instead of `docker compose`. Default: `false`
- `docker_login_user` - The username for your container registry (e.g., Docker Hub, GHCR, ECR). (Optional)
- `docker_login_password` - The password or access token for your container registry user. (Optional)
- `docker_login_registry` - The container registry hostname (e.g., `ghcr.io`, `your_aws_account_id.dkr.ecr.your_region.amazonaws.com`). If not specified, defaults to Docker Hub (`docker.io`). (Optional)

## Development & Testing

This action includes a testing workflow located at `.github/workflows/test.yml`. This workflow automatically tests various functionalities of the action upon pushes and pull requests. Key features of the testing setup include:

- **Service Container:** An SSH server (`rastasheep/ubuntu-sshd`) is run as a service container to act as a mock remote host.
- **SSH Key Management:** SSH keys are dynamically generated and configured for communication between the action and the mock SSH server.
- **Docker Command Mocking:** The `docker` command is replaced with a mock script during tests. This script logs the calls made to `docker` (e.g., `context create`, `login`, `compose`, `stack deploy`), allowing verification of the action's command construction logic without requiring a full Docker-in-Docker setup.
- **Test Cases:** The workflow includes tests for:
- Basic SSH connectivity and Docker context setup.
- File uploads using `upload_directory` and `post_upload_command`.
- Docker Swarm mode (`docker_swarm: true`).
- Docker registry login.
- Tailscale SSH mode (verifying that SSH key inputs are not strictly required by the action).

This testing suite helps ensure the reliability of the action and serves as a reference for future development.

## License

Expand Down
Loading
Loading