diff --git a/.ddev/config.yaml b/.ddev/config.yaml new file mode 100644 index 0000000..880d118 --- /dev/null +++ b/.ddev/config.yaml @@ -0,0 +1,280 @@ +name: example +type: php +docroot: web +php_version: "8.2" +webserver_type: nginx-fpm +xdebug_enabled: false +additional_hostnames: [] +additional_fqdns: [] +database: + type: mariadb + version: "10.11" +use_dns_when_possible: true +composer_version: "2" +web_environment: [] +corepack_enable: false + +# Key features of DDEV's config.yaml: + +# name: # Name of the project, automatically provides +# http://projectname.ddev.site and https://projectname.ddev.site + +# type: # backdrop, craftcms, django4, drupal, drupal6, drupal7, laravel, magento, magento2, php, python, shopware6, silverstripe, typo3, wordpress +# See https://ddev.readthedocs.io/en/stable/users/quickstart/ for more +# information on the different project types +# "drupal" covers recent Drupal 8+ + +# docroot: # Relative path to the directory containing index.php. + +# php_version: "8.2" # PHP version to use, "5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3" + +# You can explicitly specify the webimage but this +# is not recommended, as the images are often closely tied to DDEV's' behavior, +# so this can break upgrades. + +# webimage: # nginx/php docker image. + +# database: +# type: # mysql, mariadb, postgres +# version: # database version, like "10.11" or "8.0" +# MariaDB versions can be 5.5-10.8 and 10.11, MySQL versions can be 5.5-8.0 +# PostgreSQL versions can be 9-16. + +# router_http_port: # Port to be used for http (defaults to global configuration, usually 80) +# router_https_port: # Port for https (defaults to global configuration, usually 443) + +# xdebug_enabled: false # Set to true to enable Xdebug and "ddev start" or "ddev restart" +# Note that for most people the commands +# "ddev xdebug" to enable Xdebug and "ddev xdebug off" to disable it work better, +# as leaving Xdebug enabled all the time is a big performance hit. + +# xhprof_enabled: false # Set to true to enable Xhprof and "ddev start" or "ddev restart" +# Note that for most people the commands +# "ddev xhprof" to enable Xhprof and "ddev xhprof off" to disable it work better, +# as leaving Xhprof enabled all the time is a big performance hit. + +# webserver_type: nginx-fpm, apache-fpm, or nginx-gunicorn + +# timezone: Europe/Berlin +# This is the timezone used in the containers and by PHP; +# it can be set to any valid timezone, +# see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +# For example Europe/Dublin or MST7MDT + +# composer_root: +# Relative path to the Composer root directory from the project root. This is +# the directory which contains the composer.json and where all Composer related +# commands are executed. + +# composer_version: "2" +# You can set it to "" or "2" (default) for Composer v2 or "1" for Composer v1 +# to use the latest major version available at the time your container is built. +# It is also possible to use each other Composer version channel. This includes: +# - 2.2 (latest Composer LTS version) +# - stable +# - preview +# - snapshot +# Alternatively, an explicit Composer version may be specified, for example "2.2.18". +# To reinstall Composer after the image was built, run "ddev debug refresh". + +# nodejs_version: "20" +# change from the default system Node.js version to any other version. +# Numeric version numbers can be complete (i.e. 18.15.0) or +# incomplete (18, 17.2, 16). 'lts' and 'latest' can be used as well along with +# other named releases. +# see https://www.npmjs.com/package/n#specifying-nodejs-versions +# Note that you can continue using 'ddev nvm' or nvm inside the web container +# to change the project's installed node version if you need to. + +# corepack_enable: false +# Change to 'true' to 'corepack enable' and gain access to latest versions of yarn/pnpm + +# additional_hostnames: +# - somename +# - someothername +# would provide http and https URLs for "somename.ddev.site" +# and "someothername.ddev.site". + +# additional_fqdns: +# - example.com +# - sub1.example.com +# would provide http and https URLs for "example.com" and "sub1.example.com" +# Please take care with this because it can cause great confusion. + +# upload_dirs: "custom/upload/dir" +# +# upload_dirs: +# - custom/upload/dir +# - ../private +# +# would set the destination paths for ddev import-files to /custom/upload/dir +# When Mutagen is enabled this path is bind-mounted so that all the files +# in the upload_dirs don't have to be synced into Mutagen. + +# disable_upload_dirs_warning: false +# If true, turns off the normal warning that says +# "You have Mutagen enabled and your 'php' project type doesn't have upload_dirs set" + +# ddev_version_constraint: "" +# Example: +# ddev_version_constraint: ">= 1.22.4" +# This will enforce that the running ddev version is within this constraint. +# See https://github.com/Masterminds/semver#checking-version-constraints for +# supported constraint formats + +# working_dir: +# web: /var/www/html +# db: /home +# would set the default working directory for the web and db services. +# These values specify the destination directory for ddev ssh and the +# directory in which commands passed into ddev exec are run. + +# omit_containers: [db, ddev-ssh-agent] +# Currently only these containers are supported. Some containers can also be +# omitted globally in the ~/.ddev/global_config.yaml. Note that if you omit +# the "db" container, several standard features of DDEV that access the +# database container will be unusable. In the global configuration it is also +# possible to omit ddev-router, but not here. + +# performance_mode: "global" +# DDEV offers performance optimization strategies to improve the filesystem +# performance depending on your host system. Should be configured globally. +# +# If set, will override the global config. Possible values are: +# - "global": uses the value from the global config. +# - "none": disables performance optimization for this project. +# - "mutagen": enables Mutagen for this project. +# - "nfs": enables NFS for this project. +# +# See https://ddev.readthedocs.io/en/stable/users/install/performance/#nfs +# See https://ddev.readthedocs.io/en/stable/users/install/performance/#mutagen + +# fail_on_hook_fail: False +# Decide whether 'ddev start' should be interrupted by a failing hook + +# host_https_port: "59002" +# The host port binding for https can be explicitly specified. It is +# dynamic unless otherwise specified. +# This is not used by most people, most people use the *router* instead +# of the localhost port. + +# host_webserver_port: "59001" +# The host port binding for the ddev-webserver can be explicitly specified. It is +# dynamic unless otherwise specified. +# This is not used by most people, most people use the *router* instead +# of the localhost port. + +# host_db_port: "59002" +# The host port binding for the ddev-dbserver can be explicitly specified. It is dynamic +# unless explicitly specified. + +# mailpit_http_port: "8025" +# mailpit_https_port: "8026" +# The Mailpit ports can be changed from the default 8025 and 8026 + +# host_mailpit_port: "8025" +# The mailpit port is not normally bound on the host at all, instead being routed +# through ddev-router, but it can be bound directly to localhost if specified here. + +# webimage_extra_packages: [php7.4-tidy, php-bcmath] +# Extra Debian packages that are needed in the webimage can be added here + +# dbimage_extra_packages: [telnet,netcat] +# Extra Debian packages that are needed in the dbimage can be added here + +# use_dns_when_possible: true +# If the host has internet access and the domain configured can +# successfully be looked up, DNS will be used for hostname resolution +# instead of editing /etc/hosts +# Defaults to true + +# project_tld: ddev.site +# The top-level domain used for project URLs +# The default "ddev.site" allows DNS lookup via a wildcard +# If you prefer you can change this to "ddev.local" to preserve +# pre-v1.9 behavior. + +# ngrok_args: --basic-auth username:pass1234 +# Provide extra flags to the "ngrok http" command, see +# https://ngrok.com/docs/ngrok-agent/config or run "ngrok http -h" + +# disable_settings_management: false +# If true, DDEV will not create CMS-specific settings files like +# Drupal's settings.php/settings.ddev.php or TYPO3's AdditionalConfiguration.php +# In this case the user must provide all such settings. + +# You can inject environment variables into the web container with: +# web_environment: +# - SOMEENV=somevalue +# - SOMEOTHERENV=someothervalue + +# no_project_mount: false +# (Experimental) If true, DDEV will not mount the project into the web container; +# the user is responsible for mounting it manually or via a script. +# This is to enable experimentation with alternate file mounting strategies. +# For advanced users only! + +# bind_all_interfaces: false +# If true, host ports will be bound on all network interfaces, +# not the localhost interface only. This means that ports +# will be available on the local network if the host firewall +# allows it. + +# default_container_timeout: 120 +# The default time that DDEV waits for all containers to become ready can be increased from +# the default 120. This helps in importing huge databases, for example. + +#web_extra_exposed_ports: +#- name: nodejs +# container_port: 3000 +# http_port: 2999 +# https_port: 3000 +#- name: something +# container_port: 4000 +# https_port: 4000 +# http_port: 3999 +# Allows a set of extra ports to be exposed via ddev-router +# Fill in all three fields even if you don’t intend to use the https_port! +# If you don’t add https_port, then it defaults to 0 and ddev-router will fail to start. +# +# The port behavior on the ddev-webserver must be arranged separately, for example +# using web_extra_daemons. +# For example, with a web app on port 3000 inside the container, this config would +# expose that web app on https://.ddev.site:9999 and http://.ddev.site:9998 +# web_extra_exposed_ports: +# - name: myapp +# container_port: 3000 +# http_port: 9998 +# https_port: 9999 + +#web_extra_daemons: +#- name: "http-1" +# command: "/var/www/html/node_modules/.bin/http-server -p 3000" +# directory: /var/www/html +#- name: "http-2" +# command: "/var/www/html/node_modules/.bin/http-server /var/www/html/sub -p 3000" +# directory: /var/www/html + +# override_config: false +# By default, config.*.yaml files are *merged* into the configuration +# But this means that some things can't be overridden +# For example, if you have 'use_dns_when_possible: true'' you can't override it with a merge +# and you can't erase existing hooks or all environment variables. +# However, with "override_config: true" in a particular config.*.yaml file, +# 'use_dns_when_possible: false' can override the existing values, and +# hooks: +# post-start: [] +# or +# web_environment: [] +# or +# additional_hostnames: [] +# can have their intended affect. 'override_config' affects only behavior of the +# config.*.yaml file it exists in. + +# Many DDEV commands can be extended to run tasks before or after the +# DDEV command is executed, for example "post-start", "post-import-db", +# "pre-composer", "post-composer" +# See https://ddev.readthedocs.io/en/stable/users/extend/custom-commands/ for more +# information on the commands that can be extended and the tasks you can define +# for them. Example: +#hooks: diff --git a/.github/workflows/ansible-hosts b/.github/workflows/ansible-hosts new file mode 100644 index 0000000..8645f02 --- /dev/null +++ b/.github/workflows/ansible-hosts @@ -0,0 +1,2 @@ +[operations_host_ddev] +server.mydomain.com ansible_connection=local diff --git a/.github/workflows/server.test.yml b/.github/workflows/server.test.yml new file mode 100644 index 0000000..c206abc --- /dev/null +++ b/.github/workflows/server.test.yml @@ -0,0 +1,211 @@ +# +# This workflow is for testing your server's config. +# +# It will launch a docker-based test server, run your ansible playbook, and launch a site with ddev. +# +# This allows you to create pull requests to alter server config, with CI/CD to ensure it works. +# +name: Server Test +on: + pull_request: + +env: + # To install github runners automatically, you need a personal access token with admin:write permissions on your repository. + RUNNER_GITHUB_TOKEN: "${{ secrets.OUR_GITHUB_TOKEN_RUNNER_ADMIN }}" + + # The playbook to run. + # You can use the one included in the site-runn ansible roles or make your own. + # ANSIBLE_PLAYBOOK: ansible/site-runner/playbook.yml + ANSIBLE_PLAYBOOK: ansible/playbook.example.yml +# SERVER_HOSTNAME: server.mydomain.com +# RUNNER_NAME: platform@server.mydomain.com + +concurrency: + group: ${{ github.workflow }}-${{ github.event.number }} + cancel-in-progress: true + +jobs: + create-server: + name: Launch test runner + runs-on: ubuntu-latest + strategy: + matrix: + include: + - os: rockylinux9 + server_hostname: server.pr${{ github.event.number }}.rockylinux9.${{ vars.SERVER_HOSTNAME }} + steps: + - uses: actions/checkout@v6 + with: + submodules: 'recursive' + + # run-with-summary, etc + - uses: jonpugh/goatscripts@main + + - name: Install docker-compose + uses: KengoTODA/actions-setup-docker-compose@v1 + with: + version: '2.24.0' + + - name: Information + run: | + docker-compose --version + + # Site runner ansible roles now sets SSH config to allow hosts to become known on first connection. + - name: Set ansible variables + run: | + echo "operations_platform_ssh_private_key: \"${{ secrets.SSH_PRIVATE_KEY }}\"" > vars.ci.yml + echo "docker_service_manage: false" >> vars.ci.yml + cat vars.ci.yml + + echo "[operations_host_ddev] + ${{ matrix.server_hostname }} ansible_connection=local" > ./ansible/hosts + cat ./ansible/hosts + + - name: Set runner name + if: github.event_name == 'pull_request' + run: | + echo "DOCKER_SERVER_HOSTNAME=server.pr${{ github.event.number }}.${{ matrix.os }}.${{ vars.SERVER_HOSTNAME }}" >> "$GITHUB_ENV" + + - name: Set runner name + if: github.event_name != 'pull_request' + run: | + echo "DOCKER_SERVER_HOSTNAME=${{ matrix.server_hostname }}" >> "$GITHUB_ENV" + + - name: Set variables + run: | + echo "DISTRO=${{ matrix.os }}" > .env + echo "DOCKER_SERVER_HOSTNAME=${{ matrix.server_hostname }}" > .env + cat .env + + - name: Start Containers + run: | + docker compose up -d --quiet-pull + docker-compose exec operations chmod 0400 /etc/shadow + + - name: Inventory + run: | + run-with-summary \ + docker-compose exec operations \ + ansible-playbook ${ANSIBLE_PLAYBOOK} --list-hosts + env: + SUCCESS: "Ansible inventory" + ERROR: "Ansible inventory list failed" + HIDE: true + + - name: Playbook + env: + SUCCESS: "Ansible playbook completed successfully." + ERROR: "Ansible playbook failed!" + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + run: | + run-with-summary \ + docker-compose exec operations \ + ansible-playbook ${ANSIBLE_PLAYBOOK} \ + --extra-vars operations_github_api_token=${RUNNER_GITHUB_TOKEN} + +# @TODO Removed this part of the test. Couldn't get ddev in docker on github runners working. +# - name: Run jobs +# env: +# REPO: ${{ github.repository }} +# RUN_ID: ${{ github.run_id }} +# ATTEMPT: ${{ github.run_attempt }} +# JOB_NAME: ${{ github.job }} +# GH_TOKEN: ${{ github.token }} +# TIMEOUT: 600 +# run: | +# wait-for ./scripts/jobs-done + + # create-site: + # name: Create Preview Site + # uses: operations-project/github-action-ddev-runner/.github/workflows/operations.site.deploy.ddev.yml@feature/reusable-workflows + + # strategy: + # matrix: + # include: + # - os: rockylinux9 + # server_hostname: server.pr${{ github.event.number }}.rockylinux9.${{ vars.SERVER_HOSTNAME }} + + # with: + + # # Configure your site here. + # git_root: /var/platform/Sites/${{ github.repository }}/pr${{ github.event.number }} + + # # Use the http URL. + # git_repository: ${{ github.event.repository.clone_url }} + + # # Must be unique per server. + # ddev_project_name: ourproject.pr${{ github.event.number }} + + # # Used to create a system domain. + # ddev_project_tld: sites.${{ matrix.server_hostname }} + + # # Tell the remote workflow what to run on. + # github_runs_on: platform@${{ matrix.server_hostname }} + + # # Define the github environment name, to be displayed in the UI. + # github_environment_name: pr${{ github.event.number }} + + # # Define a github environment url, a link to be shown on the pull request. + # github_environment_url: http://pr${{ github.event.number }}.sites.${{ matrix.server_hostname }} + + # # To persist a site's data, set "run_prepare_command" to false. + # run_prepare_command: true + # prepare_command: echo "Preparing site..." + + # # Command to run after deploying code. + # deploy_command: ddev exec echo "Hello from $(hostname)!" + + # # Additional ddev config to apply to the environment. + # # Will be saved to .ddev/config.zzz.runner.yaml + # ddev_config: | + # additional_fqdns: + # - admin.pr${{ github.event.number }}.sites.${{ matrix.server_hostname }} + # - ddev-runner.ddev.site + + # run-command: + # name: DDEV Status + # uses: operations-project/github-action-ddev-runner/.github/workflows/operations.site.command.yml@feature/reusable-workflows + # needs: create-site + # strategy: + # matrix: + # include: + # - os: rockylinux9 + # server_hostname: server.pr${{ github.event.number }}.rockylinux9.${{ vars.SERVER_HOSTNAME }} + + # with: + # working_directory: /var/platform/Sites/${{ github.repository }}/pr${{ github.event.number }} + # github_runs_on: platform@${{ matrix.server_hostname }} + # command: ddev status + # env: | + # SUCCESS="DDEV Status" + # HIDE=1 + + # test-site: + # name: Run tests + # needs: create-site + # runs-on: ${{ matrix.server_hostname }} + + # strategy: + # matrix: + # include: + # - os: rockylinux9 + # server_hostname: server.pr${{ github.event.number }}.rockylinux9.${{ vars.SERVER_HOSTNAME }} + + # steps: + + # - uses: jonpugh/goatscripts@main + # - name: Check homepage for Hello World. + # env: + # SUCCESS: "Tests passed! DDEV webserver is online. :boom:" + # ERROR: "Unable to load DDEV website. :x:" + # run: | + # run-with-summary curl https://ddev-runner.ddev.site + # curl -s https://ddev-runner.ddev.site | grep "Hello World!" + + # remove-site: + # name: Remove Site + # uses: operations-project/github-action-ddev-runner/.github/workflows/operations.site.destroy.ddev.yml@feature/reusable-workflows + # needs: test-site + # with: + # git_root: /var/platform/Sites/${{ github.repository }}/pr${{ github.event.number }} + # github_runs_on: platform@${{ vars.SERVER_NAME }} diff --git a/.github/workflows/site.preview.yml b/.github/workflows/site.preview.yml new file mode 100644 index 0000000..b1bb5c0 --- /dev/null +++ b/.github/workflows/site.preview.yml @@ -0,0 +1,122 @@ +# +# This workflow is for testing the re-usable ones workflows. +# +# It contains multiple steps, including starting and tearing down a site. +# It doesn't make sense to use this example directly. +# +# See the example.* files for that. +# +name: Preview Sites +on: + pull_request: + +env: + # To install github runners automatically, you need a personal access token with admin:write permissions on your repository. + GITHUB_TOKEN: "${{ secrets.OUR_GITHUB_TOKEN_RUNNER_ADMIN }}" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.number }} + cancel-in-progress: false + +jobs: + + # To allow sites to exist across multiple jobs, we can use a runner to launch other runners. + # Once you have this workflow going on a site runner server, you can remove the create-server job. + create-server: + name: Launch test runner + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + repository: 'operations-project/github-runner-starter' + ref: 'v1.2.1' + + # Kick off the runner script over and over until there are no more queued jobs. + - name: "Launch runner script." + + run: | + while [[ $(curl -s -H "Authorization: token ${GITHUB_TOKEN}" "https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs" | jq -r '.jobs[] | select(.status=="queued") | .id' | wc -l) -gt 0 ]]; do + sleep 2 + ./github-runner-starter \ + --run \ + --name=github.actions.runner.${{ github.run_id }}.${{ matrix.runner }} \ + --labels=github.actions.runner.${{ github.run_id }} \ + --config-sh-options=--ephemeral + sleep 2 + done + + create-site: + name: Create Preview Site + uses: operations-project/github-action-ddev-runner/.github/workflows/operations.site.deploy.ddev.yml@feature/reusable-workflows + with: + + # Configure your site here. + git_root: /home/runner/ourproject/pr${{ github.event.number }} + + # Use the http URL. + git_repository: ${{ github.event.repository.clone_url }} + + # Must be unique per server. + ddev_project_name: ourproject.pr${{ github.event.number }} + + # Used to create a system domain. + ddev_project_tld: sites.thinkdrop.net + + # Tell the remote workflow what to run on. + # Once a site runner server is installed, use that server's name (eg. platform@server.mydomain.com) + github_runs_on: github.actions.runner.${{ github.run_id }} + + # Define the github environment name, to be displayed in the UI. + github_environment_name: pr${{ github.event.number }} + + # Define a github environment url, a link to be shown on the pull request. + github_environment_url: http://pr${{ github.event.number }}.sites.thinkdrop.net + + # To persist a site's data, set "run_prepare_command" to false. + run_prepare_command: true + prepare_command: echo "Preparing site..." + + # Command to run after deploying code. + deploy_command: ddev exec echo "Hello from $(hostname)!" + + # Additional ddev config to apply to the environment. + # Will be saved to .ddev/config.zzz.runner.yaml + ddev_config: | + additional_fqdns: + - admin.pr${{ github.event.number }}.sites.thinkdrop.net + - ddev-runner.ddev.site + + run-command: + name: DDEV Status + uses: operations-project/github-action-ddev-runner/.github/workflows/operations.site.command.yml@feature/reusable-workflows + needs: create-site + with: + working_directory: /home/runner/ourproject/pr${{ github.event.number }} + github_runs_on: github.actions.runner.${{ github.run_id }} + command: ddev status + env: | + SUCCESS="DDEV Status" + HIDE=1 + + test-site: + name: Run tests + needs: run-command + runs-on: github.actions.runner.${{ github.run_id }} + steps: + + - uses: jonpugh/goatscripts@main + - name: Check homepage for Hello World. + env: + SUCCESS: "Tests passed! DDEV webserver is online. :boom:" + ERROR: "Unable to load DDEV website. :x:" + run: | + run-with-summary curl https://ddev-runner.ddev.site + curl -s https://ddev-runner.ddev.site | grep "HELLO WORLD" + + remove-site: + name: Remove Site + uses: operations-project/github-action-ddev-runner/.github/workflows/operations.site.destroy.ddev.yml@feature/reusable-workflows + needs: test-site + with: + git_root: /home/runner/ourproject/pr${{ github.event.number }} + github_runs_on: github.actions.runner.${{ github.run_id }} diff --git a/.github/workflows/site.preview.yml.example b/.github/workflows/site.preview.yml.example new file mode 100644 index 0000000..2b79b64 --- /dev/null +++ b/.github/workflows/site.preview.yml.example @@ -0,0 +1,73 @@ +# +# site.preview.yml +# +# Launch and test preview sites for each pull request. +# +name: Preview Site +on: + pull_request: + +# Cancel jobs if another push is received. +concurrency: + group: ${{ github.workflow }}-${{ github.event.number }} + cancel-in-progress: false + +env: + SITE_URL: http://pr${{ github.event.number }}.server.mydomain.com + +jobs: + create-site: + name: Create Preview Site + uses: operations-project/github-action-ddev-runner/.github/workflows/operations.site.deploy.ddev.yml@feature/reusable-workflows + with: + + # Where to install your site. + # By default, the site runner ansible role creates user "platform" at "/var/platform" + git_root: /var/platform/Sites/${{ github.repository }}/pr${{ github.event.number }} + + # Project name for this instance. Used to generate the domain name. + ddev_project_name: example.pr${{ github.event.number }} + + # Top-level domain. Sites are hosted as subdomains under this. + ddev_project_tld: server.mydomain.com + + # Tell the remote workflow what to run on. + # For site runner, the default is "platform@{hostname -f}" + # This is defined in the site runner playbook, but can be controlled with variables. + github_runs_on: platform@server.mydomain.com + + # Define the github environment name, to be displayed in the UI. + github_environment_name: pr${{ github.event.number }} + + # Define a github environment url, a link to be shown on the pull request. + github_environment_url: http://example.pr${{ github.event.number }}.server.mydomain.com + + # To persist a site's data, set "run_prepare_command" to false. + run_prepare_command: true + + # Prepare the site's data. Run your sync/import/install script. + # Change to 'ddev drush site:install' or 'ddev sync' or whatever your project uses to prepare the site data. + prepare_command: ddev status + + # Command to run after deploying code changes + # Change to 'ddev drush update:database' or './deploy.sh' or whatever your project uses to after new code is available. + deploy_command: ddev status + + # Additional ddev config to apply to the environment. + # Will be saved to .ddev/config.zzz.runner.yaml + # See your project's .ddev/config.yaml file for examples. + ddev_config: | + + # Test .github/workflows/operations.site.command.yml + run-command: + name: DDEV Status + uses: operations-project/github-action-ddev-runner/.github/workflows/operations.site.command.yml@feature/reusable-workflows + needs: create-site + with: + working_directory: /var/platform/Sites/${{ github.repository }}/pr${{ github.event.number }} + github_runs_on: platform@server.mydomain.com + env: | + SUCCESS="DDEV Status Command" + HIDE=1 + command: | + ddev status diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..724ef59 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "site-runner"] + path = ansible/site-runner + url = https://github.com/operations-project/ansible-collection-site-runner.git diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..01d55b5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +# This is for testing and development only. +# SSHD must be installed before running the playbook. +# Thanks: https://stackoverflow.com/questions/71040681/qemu-x86-64-could-not-open-lib64-ld-linux-x86-64-so-2-no-such-file-or-direc +FROM geerlingguy/docker-${DISTRO:-rockylinux10}-ansible:latest + +# https://github.com/geerlingguy/docker-rockylinux9-ansible/issues/6#issuecomment-2676248714 +RUN chmod 0400 /etc/shadow diff --git a/README.md b/README.md index 5f67be2..3ac2499 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,42 @@ -# site-runner-example-app +# Site Runner Example App An example repo containing server configuration and app code. + +## Instructions + +This repo was setup in concert with writing the instructions from https://github.com/operations-project/ansible-collection-site-runner/tree/feature/how-to?#how-to + +## Variables + +To get started you need the following bits of info. + +- Server Hostname. Use a FQDN with a DNS record for ease of use. +- GitHub usernames of your server admins. +- Name of your app repository. +- A GitHub token with repo admin privileges. Used for creating self-hosted runners. + +To set this up for your own host, just copy this repo and change the following files in the [`ansible`](./ansible) directory: + +- [`hosts`](./ansible/hosts) - Define server hostname. Defines your Ansible inventory. See [Ansible Documentation](https://docs.ansible.com/ansible/latest/inventory_guide/intro_inventory.html). + ```ini + [operations_host_ddev] + server.mydomain.com ansible_connection=local + ``` +- [`group_vars/all.yml`](./ansible/group_vars/all.yml) - Define server admins. Add github usernames. + ```yml + operations_admin_users: + - jonpugh + ``` +- [`host_vars/server.mydomain.com.yml`](./ansible/host_vars/server.mydomain.com.yml) - Define which repos go on the server. Rename to match your hostname. + ```yml + operations_github_runners: + - runner_repo: operations-project/site-runner-example-app + ``` + +Set the secret variable `operations_github_api_token` to the GitHub token using your preferred secrets management tools. + +For example, to use github secrets, you can pass it on the command line in your workflow file: +``` +ansible-playbook --extra-vars operations_github_api_token=${{ secrets.REPO_ADMIN_TOKEN }} +``` + +See [`server.test.yml`](./.github/workflows/server.yml#53) diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..30e5705 --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,18 @@ +# These settings work from the repo root, given that the inventory and roles are in ./ansible. + +[defaults] +result_format = yaml +force_color = True + +# You don't have to set /etc/ansible if you set the inventory path. +# When setting inventory, the host_vars and group_vars will be loaded automatically. +inventory =./ansible/hosts, ./ansible/inventory.example.yml + +# The ./roles path next to the playbook is used automatically. +# If you are using your own playbook, you will have to make sure the site-runner roles are available +# by setting roles_path. + + +# It's not really recommended to install roles here because you want to run the roles from your git repo. +# The roles in your repo are cloned to the runner's working directory are used instead. +# roles_path = /etc/ansible/roles diff --git a/ansible/README.md b/ansible/README.md new file mode 100644 index 0000000..545c6cf --- /dev/null +++ b/ansible/README.md @@ -0,0 +1,7 @@ +# Ansible Server Configuration + +Our server configuration. + +- `hosts` - list of servers. +- `group_vars/all.yml` - Global variables. Users. +- `group_vars/yourservername.yml` - Server specific config. diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 0000000..dad3e96 --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,14 @@ +# This file can be placed at /etc/ansible to set global config. +# Typically, however, the 'ansible' command will be run from your repo root, so it will use the +# ansible.cfg file there. + + +# [defaults] +# stdout_callback = yaml +# force_color = True + +# The ./roles path next to the playbook is used automatically. + +# It's not really recommended to install roles here because you want to run the roles from your git repo. +# The roles in your repo are cloned to the runner's working directory are used instead. +# roles_path = /etc/ansible/roles diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml new file mode 100644 index 0000000..0a14aec --- /dev/null +++ b/ansible/group_vars/all.yml @@ -0,0 +1,17 @@ + +# Put your github usernames here. +# Each one will get a user with ssh and sudo acccess. + +operations_admin_users: + - jonpugh + +# Load SSH private key from environment variable SSH_PRIVATE_KEY. +# When running on github actions, this variable will be set, loaded from github secrets. +# See server.test.yml +# +# If running manually, set SSH_PRIVATE_KEY env var if you want ansible-playbook to install it. +# You can also just leave this out and figure out your own key management. +# +# This key will be written to the platform user's authorized_keys file. +# Site codebases will be cloned with this key. +operations_platform_ssh_private_key: "{{ lookup('env', 'SSH_PRIVATE_KEY') }}" diff --git a/ansible/host_vars/server.company.com.yml b/ansible/host_vars/server.company.com.yml new file mode 100644 index 0000000..55471fd --- /dev/null +++ b/ansible/host_vars/server.company.com.yml @@ -0,0 +1,12 @@ +# +# Example host inventory file. +# +# This is a standard Ansible host inventory file. +# It is not strictly necessary. +# +# You can use inventory.yml or this. +# +# It stores variables for the host with the same name as the file. +# You could use this for secrets storage if you are comfortable putting it right on the server. +# But really, you should save your secrets in GitHub secrets and pass to ansible-playbook in your GitHub workflows. +# diff --git a/ansible/hosts b/ansible/hosts new file mode 100644 index 0000000..2d01653 --- /dev/null +++ b/ansible/hosts @@ -0,0 +1,9 @@ +# +# Ansible hosts file. +# This is standard Ansible inventory in INI format. +# Lives at /etc/ansible/hosts +# This is a good way to tell a server who it is. Use ansible_connection=local +# so that ansible-playbook runs from this server run locally. +# +[operations_host_ddev] +server.mydomain.com ansible_connection=local diff --git a/ansible/inventory.example.yml b/ansible/inventory.example.yml new file mode 100644 index 0000000..e377f05 --- /dev/null +++ b/ansible/inventory.example.yml @@ -0,0 +1,41 @@ +# +# Example server inventory file. +# + +# "All" Group +# Put config that should be applied to all servers here. +all: + vars: + operations_admin_users: + - this_user_is_super + +# DDEV Site Runners +operations_host_ddev: + vars: + # The github repos to install on this server. + # Each of these projects will get a github self-hosted runner. + operations_github_runners: + - runner_repo: operations-project/site-runner-example-app + + # To have control over what repo is used on what server, set var operations_github_runners on specific hosts. +# hosts: +# +# # Your server's full name. +# # It will be helpful to set a DNS record for this. +# server.mydomain.com: +# +# # The github repos to install on this server. +# # Each of these projects will get a github self-hosted runner. +# operations_github_runners: +# - runner_repo: operations-project/site-runner-example-app +# +# # GitHub API token with admin:write access to the git repos. +# # https://github.com/settings/personal-access-tokens +# # DO NOT COMMIT THIS TO GIT. +# # There are many ways to store your secrets in ansible. +# # If you are running `ansible-playbook` in a GitHub action, you can simply +# # add a GitHub secret to your repo or organization, then pass ${{ secrets.YOUR_GITHUB_SECRET }} +# # to ansible-playbook +# # ansible-playbook playbook.yml --extra-vars operations_github_api_token=${{ secrets.YOUR_GITHUB_SECRET }} on the command line. +# # See .github/workflows/site-runner-test.yml line 78 for an example: +# operations_github_api_token: "" diff --git a/ansible/playbook.example.yml b/ansible/playbook.example.yml new file mode 100644 index 0000000..689cea2 --- /dev/null +++ b/ansible/playbook.example.yml @@ -0,0 +1,59 @@ +# +# This playbook was copied from the Site Runner Ansible repo: https://github.com/operations-project/ansible-collection-site-runner/blob/main/playbook.yml +# +# This grants you total control what happens on your server. +# To use this playbook, just make sure the roles are available by setting roles_path or +# using the symlink. +# +--- +- name: Configure Server + hosts: all + become: true + + pre_tasks: + + # Set server hostnames to inventory hostname. + - name: "Set hostname for {{ ansible_nodename }} to {{ inventory_hostname }}." + hostname: + name: "{{ inventory_hostname }}" + when: inventory_hostname != ansible_nodename + register: result + until: result.failed != true + retries: 5 + delay: 2 + + - name: Prerequisite Packages + package: + name: + - openssh-server + - git + - gpg + + roles: + - role: geerlingguy.security + tags: system + vars: + security_sudoers_passwordless: "{{ [operations_control_user|default('control')] + operations_admin_users|default([]) }}" + security_ssh_allowed_users: "{{ [operations_control_user|default('control')] + [operations_platform_user|default('platform')] + operations_admin_users|default([]) }}" + + - role: geerlingguy.github-users + tags: system + vars: + github_users: "{{ operations_admin_users | default([]) }}" + +- name: "Configure Hosting: DDEV" + hosts: operations_host_ddev + become: true + + roles: + - role: geerlingguy.docker + tags: system + + - role: operations.users + tags: operations + + - role: operations.ddev + tags: operations, ddev + + - role: operations.runner + tags: operations, runner diff --git a/ansible/roles b/ansible/roles new file mode 120000 index 0000000..2107a37 --- /dev/null +++ b/ansible/roles @@ -0,0 +1 @@ +site-runner/roles/ \ No newline at end of file diff --git a/ansible/site-runner b/ansible/site-runner new file mode 160000 index 0000000..aa5d107 --- /dev/null +++ b/ansible/site-runner @@ -0,0 +1 @@ +Subproject commit aa5d107a89977d89de1986688228b068b2c1bc57 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..abf00f3 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,19 @@ +# For testing and development. + +--- +services: + operations: + image: geerlingguy/docker-${DISTRO:-rockylinux9}-ansible:latest + hostname: ${DOCKER_SERVER_HOSTNAME:-server.docker.host} + cgroup: host + cgroup_parent: docker.slice + privileged: true + working_dir: /app + volumes: + - ./:/app + - /sys/fs/cgroup:/sys/fs/cgroup:rw + environment: + - OPERATIONS_PLATFORM=yes + env_file: + - .env + build: ./ diff --git a/scripts/jobs-done b/scripts/jobs-done new file mode 100755 index 0000000..e540dbd --- /dev/null +++ b/scripts/jobs-done @@ -0,0 +1,36 @@ +#!/bin/bash + +set -e +# Usage +# +# REPO owner/repo +# RUN_ID the run to check. +# ATTEMPT The run attempt. +# JOB_NAME If supplied, only check this job. +# EXCLUDE_JOB_NAME if supplied, exclude the job with this name. +# In order to use this script (jobs-done) in a job, you have to exclude itself or it will run forever. +# + +REPO=${REPO:-$(gh repo view --json 'nameWithOwner' --jq '.nameWithOwner')} +echo $REPO + +if [[ -n $JOB_NAME ]]; then + JQ=".jobs[] | select(.name = \"$JOB_NAME\") | select(.status != \"completed\") | .status" + +elif [[ -n $EXCLUDE_JOB_NAME ]]; then + JQ=".jobs[] | select(.name != \"$EXCLUDE_JOB_NAME\") | select(.status != \"completed\") | .status" + +else + JQ=".jobs[] | select(.status != \"completed\") | .status" + +fi + +JOBS=$(gh api /repos/$REPO/actions/runs/$RUN_ID/attempts/$ATTEMPT/jobs \ + | jq -e "$JQ") + +if [[ -n $JOBS ]]; then + echo "Jobs are still running." + exit 1 +else + echo "Jobs done!" +fi \ No newline at end of file diff --git a/scripts/wait-for-no-jobs b/scripts/wait-for-no-jobs new file mode 100755 index 0000000..cbe5317 --- /dev/null +++ b/scripts/wait-for-no-jobs @@ -0,0 +1,84 @@ +#!/bin/bash + +# Configuration +GITHUB_TOKEN="$GITHUB_TOKEN" # Your GitHub Personal Access Token (from env) +REPO="$REPO" # Your repository name (from env) +RUN_ID="$RUN_ID" # The specific workflow run ID (from env) +JOB_ID="$JOB_ID" # The *current* job's name (from env) +INTERVAL_SECONDS=10 # How often to check the status + +# Function to get *overall* workflow run status +get_workflow_run_status() { + curl -s -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/$REPO/actions/runs/$RUN_ID" | \ + jq -r '.status' +} + +# Function to check if all *other* jobs are completed +check_other_jobs_status() { + echo "Checking status of other jobs..." + local non_completed_count + + # Fetches all jobs, filters out the current job (JOB_ID), + # counts how many of the *other* jobs are NOT "completed". + non_completed_count=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/$REPO/actions/runs/$RUN_ID/jobs" | \ + jq --arg jid "$JOB_ID" ' + .jobs | map( + # Select jobs that are NOT the current job AND are NOT completed + select(.name != $jid and .status != "completed") + ) | length + ') + + # Note: I changed --argjson to --arg for string comparison + + if [ -z "$non_completed_count" ]; then + echo "Error: Could not parse job statuses. Check JOB_ID ($JOB_ID) and API response." + return 1 # Indicate error + elif [ "$non_completed_count" -eq 0 ]; then + # All other jobs are completed + return 0 + else + # Some other jobs are still running + echo "$non_completed_count other jobs are not yet completed." + return 1 # Indicate "still waiting" + fi +} + + +echo "Waiting for GitHub Actions workflow run $RUN_ID to complete..." +echo "Repository: $REPO" +echo "Run URL: https://github.com/$REPO/actions/runs/$RUN_ID" +echo "Ignoring Job ID: $JOB_ID" + +while true; do + STATUS=$(get_workflow_run_status) + echo "Current overall run status: $STATUS" + + case "$STATUS" in + "completed") + echo "Workflow run $RUN_ID completed." + # You could also check the 'conclusion' (success, failure, etc.) here if needed + exit 0 + ;; + "queued"|"in_progress"|"waiting"|"requested"|"pending") + echo "Workflow run $RUN_ID is still $STATUS." + + # This implements your @TODO: + # Check if all *other* jobs are done. + if check_other_jobs_status; then + echo "All other jobs are completed. This job (JOB_ID: $JOB_ID) can now proceed." + exit 0 + else + echo "Still waiting for other jobs to finish... sleeping for $INTERVAL_SECONDS sec." + sleep $INTERVAL_SECONDS + fi + ;; + *) + echo "Unexpected status: $STATUS. Exiting." + exit 1 + ;; + esac +done diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..4e3dffe --- /dev/null +++ b/web/index.html @@ -0,0 +1 @@ +HELLO WORLD