├── README.md ├── action.yml ├── entrypoint.sh └── pr-deploy.sh /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | This GitHub Action deploys pull requests in Docker containers, allowing you to test your changes in an isolated environment before merging. This documentation has been sectioned into two; First section for Contributors and the Second section for User 4 | 5 | 6 | 7 | ## For Users 8 | 9 | ## Usage 10 | Pull request events trigger this action. It builds the Docker image for the pull request and deploys it in a new Docker container. This step calls the action tool in the workflow script 11 | ``` 12 | steps: 13 | - uses: hngprojects/pr-deploy@v2.0.1 14 | ``` 15 | ## Inputs 16 | The following inputs are required to configure the GitHub Action. These inputs allow the action to connect to your server, specify the: 17 | - context, that is where the Dockerfile is located. 18 | - define the Dockerfile for deployment. 19 | - set the environmental variables in Github secrets. 20 | 21 | The environmental variables needed for this tool version are: 22 | - SERVER_HOST 23 | - SERVER_PASSWORD 24 | - SERVER_USERNAME 25 | - SERVER_PORT 26 | 27 | ### Example Workflow File 28 | To help you get started quickly, here’s an example of configuring your GitHub Actions workflow file to deploy pull requests in Docker containers using this GitHub Action. This setup allows you to automatically build and deploy your application whenever a pull request is opened, ensuring your changes are tested in an isolated environment. 29 | Your workflow file should be formatted as follows: 30 | ``` 31 | name: PR Deploy 32 | on: 33 | pull_request: 34 | types: [opened, synchronize, reopened, closed] 35 | 36 | jobs: 37 | deploy-pr: 38 | environment: 39 | name: preview 40 | url: ${{ steps.deploy.outputs.preview-url }} 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Checkout to branch 44 | uses: actions/checkout@v4 45 | - id: deploy 46 | name: Pull Request Deploy 47 | uses: hngprojects/pr-deploy@2.0.1 48 | with: 49 | server_host: ${{ secrets.SERVER_HOST }} 50 | server_username: ${{ secrets.SERVER_USERNAME }} 51 | server_password: ${{ secrets.SERVER_PASSWORD }} 52 | server_port: ${{ secrets.SERVER_PORT }} 53 | comment: true 54 | context: '.' 55 | dockerfile: 'Dockerfile' 56 | exposed_port: '5000' 57 | host_volume_path: '/var/' 58 | container_volume_path: '/var/' 59 | github_token: ${{ secrets.GITHUB_TOKEN }} 60 | - name: Print Preview Url 61 | run: | 62 | echo "Preview Url: ${{ steps.deploy.outputs.preview-url }}" 63 | ``` 64 | **NOTE** 65 | 1. Github token is autogenerated, please do not declare it. 66 | 2. You have the option to allow or disallow the Comment feature with the `true or false` argument. This feature lets you know the status of the deployment. 67 | 3. Context feature specifies the location of your Dockerfile. 68 | 69 | ## For Contributors 70 | ### Overview 71 | These tool made use of two files named: 72 | - action.yml 73 | - entrypoint.sh 74 | 75 | `action.yml`: 76 | The file declares the inputs needed to deploy the pull request in isolated docker containers. 77 | 78 | `entrypoint.sh`: 79 | With the aid of the inputs retrieved by the actions.yml file, the shell script automates the deployment process. 80 | 81 | ### How to Contribute 82 | 1. **Fork the Repository**: Start by forking the repository to your GitHub account. 83 | 84 | 2. **Create a New Branch**: Create a branch for your feature or bugfix 85 | ``` 86 | git checkout -b feature/your-feature-name 87 | ``` 88 | 3. **Modify Inputs**: If adding new inputs or modifying existing ones: 89 | - Ensure they are documented in action.yml. 90 | - Update the shell script to handle these inputs appropriately. 91 | 92 | 4. **Update Documentation**: Reflect any changes in the README.md file. 93 | 94 | 5. **Commit Your Changes**: With a descriptive message: 95 | ``` 96 | git commit -m 'Add feature: your-feature-name' 97 | 98 | ``` 99 | 100 | 6. **Push to Your Branch**: 101 | ``` 102 | git push origin feature/your-feature-name 103 | 104 | ``` 105 | 106 | 107 | ## Troubleshooting tips: 108 | 109 | - If the action fails, check the GitHub Actions logs for detailed error messages. 110 | - Ensure that your server's firewall allows incoming connections on the SSH port and the random port range used for deployments. 111 | - Verify that the server has sufficient resources (CPU, memory, disk space) to run multiple Docker containers. 112 | 113 | ## Best practices: 114 | 115 | - Regularly update the action to the latest version to benefit from bug fixes and improvements. 116 | - Use environment variables for sensitive information instead of hardcoding them in your Dockerfile or start command. 117 | - Implement proper access controls on your deployment server to ensure security. 118 | - Regularly clean up unused containers and images to conserve server resources. 119 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "Pull Request Deploy" 2 | description: "A github action to automate the deployment of pull requests" 3 | branding: 4 | icon: "cloud" 5 | color: "blue" 6 | 7 | inputs: 8 | server_host: 9 | description: SSH host of the server 10 | required: true 11 | server_username: 12 | description: SSH username 13 | required: true 14 | server_password: 15 | description: SSH password 16 | required: false 17 | server_port: 18 | description: SSH port 19 | required: false 20 | default: 22 21 | server_private_key: 22 | description: SSH private key 23 | required: false 24 | context: 25 | description: Directory in the repository where the Dockerfile or start command is located 26 | required: false 27 | default: "./" 28 | dockerfile: 29 | description: Path to the Dockerfile (optional) 30 | required: false 31 | default: "./Dockerfile" 32 | exposed_port: 33 | description: Port to expose in the container 34 | required: true 35 | envs: 36 | description: Environment variables to pass to the container (multi-line string) 37 | required: false 38 | host_volume_path: 39 | description: This is the path on the host machine you want to map to the container 40 | required: false 41 | container_volume_path: 42 | description: This is the path on the container you want to map to 43 | required: false 44 | github_token: 45 | description: GitHub token to authenticate API requests 46 | required: true 47 | comment: 48 | description: A boolean value stating if the comments feature should be provided 49 | required: false 50 | default: false 51 | 52 | outputs: 53 | preview-url: 54 | description: "Preview URL" 55 | value: ${{ steps.execute-deployment.outputs.preview-url }} 56 | 57 | runs: 58 | using: 'composite' 59 | steps: 60 | - name: Docker Build 61 | if: github.event.action != 'closed' 62 | shell: bash 63 | run: | 64 | PR_ID="pr_${{ github.event.repository.id }}_${{ github.event.number }}" 65 | cd "${{ inputs.context }}" 66 | docker build -t $PR_ID -f "${{ inputs.dockerfile }}" . 67 | docker save $PR_ID | gzip > "/tmp/${PR_ID}.tar.gz" 68 | - id: execute-deployment 69 | name: Execute deployment 70 | run: | 71 | cd ${{ github.action_path }} && bash entrypoint.sh 72 | shell: bash 73 | env: 74 | SERVER_HOST: ${{ inputs.server_host }} 75 | SERVER_USERNAME: ${{ inputs.server_username }} 76 | SERVER_PASSWORD: ${{ inputs.server_password }} 77 | SERVER_PORT: ${{ inputs.server_port }} 78 | SERVER_PRIVATE_KEY: ${{ inputs.server_private_key }} 79 | CONTEXT: ${{ inputs.context }} 80 | DOCKERFILE: ${{ inputs.dockerfile }} 81 | EXPOSED_PORT: ${{ inputs.exposed_port }} 82 | ENVS: ${{ inputs.envs }} 83 | COMMENT: ${{ inputs.comment }} 84 | REPO_URL: ${{ github.event.repository.clone_url }} 85 | REPO_OWNER: ${{ github.repository_owner }} 86 | REPO_NAME: ${{ github.event.repository.name }} 87 | PR_NUMBER: ${{ github.event.number }} 88 | PR_ACTION: ${{ github.event.action }} 89 | CONTAINER_VOLUME_PATH: ${{ inputs.container_volume_path }} 90 | HOST_VOLUME_PATH: ${{ inputs.host_volume_path }} 91 | PR_ID: "pr_${{ github.event.repository.id }}_${{ github.event.number }}" 92 | GITHUB_TOKEN: ${{ inputs.github_token }} 93 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit immediately if a command exits with a non-zero status. 4 | set -e 5 | 6 | echo "REPOSITORY URL: $REPO_URL" 7 | 8 | # Ensure sshpass is installed 9 | if ! command -v sshpass &> /dev/null; then 10 | sudo apt-get update 11 | sudo apt-get install -y sshpass 12 | fi 13 | 14 | # Check if private key is provided, and if yes, set up a .pem file for auth 15 | if [ -n "$SERVER_PRIVATE_KEY" ]; then 16 | echo "$SERVER_PRIVATE_KEY" > private_key.pem 17 | chmod 600 private_key.pem 18 | 19 | SSH_CMD="ssh -i private_key.pem -o StrictHostKeyChecking=no -p $SERVER_PORT $SERVER_USERNAME@$SERVER_HOST" 20 | 21 | # Copy the script to the remote server. 22 | scp -i private_key.pem -o StrictHostKeyChecking=no -P $SERVER_PORT pr-deploy.sh $SERVER_USERNAME@$SERVER_HOST:/tmp/ >/dev/null 23 | 24 | # Check if PR_ACTION is not 'closed' 25 | if [ "$PR_ACTION" != "closed" ]; then 26 | # Copy the Image build zip file to the remote server 27 | scp -i private_key.pem -o StrictHostKeyChecking=no -P $SERVER_PORT "/tmp/${PR_ID}.tar.gz" $SERVER_USERNAME@$SERVER_HOST:"/tmp/${PR_ID}.tar.gz" >/dev/null 28 | fi 29 | else 30 | SSH_CMD="sshpass -p $SERVER_PASSWORD ssh -o StrictHostKeyChecking=no -p $SERVER_PORT $SERVER_USERNAME@$SERVER_HOST" 31 | 32 | # Copy the script to the remote server. 33 | sshpass -p "$SERVER_PASSWORD" scp -o StrictHostKeyChecking=no -P $SERVER_PORT pr-deploy.sh $SERVER_USERNAME@$SERVER_HOST:/tmp/ >/dev/null 34 | 35 | 36 | # Check if PR_ACTION is not 'closed' 37 | if [ "$PR_ACTION" != "closed" ]; then 38 | # Copy the Image build zip file to the remote server 39 | sshpass -p "$SERVER_PASSWORD" scp -o StrictHostKeyChecking=no -P $SERVER_PORT "/tmp/${PR_ID}.tar.gz" $SERVER_USERNAME@$SERVER_HOST:"/tmp/${PR_ID}.tar.gz" >/dev/null 40 | fi 41 | fi 42 | 43 | # Stream the output from the remote script to local terminal and save it to a log file 44 | $SSH_CMD \ 45 | "GITHUB_TOKEN='$GITHUB_TOKEN' \ 46 | CONTEXT='$CONTEXT' \ 47 | DOCKERFILE='$DOCKERFILE' \ 48 | EXPOSED_PORT='$EXPOSED_PORT' \ 49 | ENVS='$ENVS' \ 50 | COMMENT='$COMMENT' \ 51 | REPO_OWNER='$REPO_OWNER' \ 52 | REPO_NAME='$REPO_NAME' \ 53 | REPO_URL='$REPO_URL' \ 54 | REPO_ID='$REPO_ID' \ 55 | BRANCH='$GITHUB_HEAD_REF' \ 56 | PR_ID='$PR_ID' \ 57 | PR_ACTION='$PR_ACTION' \ 58 | PR_NUMBER='$PR_NUMBER' \ 59 | HOST_VOLUME_PATH='$HOST_VOLUME_PATH' \ 60 | CONTAINER_VOLUME_PATH='$CONTAINER_VOLUME_PATH' \ 61 | COMMENT_ID='$COMMENT_ID' \ 62 | bash -c 'echo $SERVER_PASSWORD | sudo -SE bash \"/tmp/pr-deploy.sh\"'" | tee "/tmp/preview_${GITHUB_RUN_ID}.txt" 63 | 64 | PREVIEW_URL=$(tail -n 1 "/tmp/preview_${GITHUB_RUN_ID}.txt") 65 | echo "preview-url=${PREVIEW_URL}" >> $GITHUB_OUTPUT 66 | -------------------------------------------------------------------------------- /pr-deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | trap 'cleanup; comment "Failed ❌"; exit 1' ERR 5 | 6 | DEPLOY_FOLDER="/srv/pr-deploy" 7 | PID_FILE="/srv/pr-deploy/nohup.json" 8 | COMMENT_ID_FILE="/srv/pr-deploy/comments.json" 9 | 10 | comment() { 11 | # Check if comments are enabled 12 | if [ "$COMMENT" != true ]; then 13 | return 14 | fi 15 | 16 | local status_message=$1 17 | echo $status_message 18 | 19 | local comment_body=$(jq -n --arg body "Here are the latest updates on your deployment. Explore the action and ⭐ star our project for more insights! 🔍 20 |
Deployed By | 24 |Status | 25 |Preview URL | 26 |Updated At (UTC) | 27 |
---|---|---|---|
PR Deploy | 32 |${status_message} | 33 |Visit Preview | 34 |$(date +'%b %d, %Y %I:%M%p') | 35 |