diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..63912be --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.git +node_modules +log +tmp diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5f8b892 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +# .editorconfig +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.rb] +# Ruby-specific settings +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100755 index 0000000..529efc5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +name: Linting + +on: + workflow_dispatch: + pull_request: + paths-ignore: + - '.github/**' + - '.pipeline/**' + - 'README.md' + +concurrency: + # github.workflow: name of the workflow + # github.ref: "refs/heads/", "refs/tags/" or "refs/pull//merge" + group: ${{ github.workflow }}-${{ github.ref }}-CI + # Cancel in-progress runs when a new workflow with the same group name is triggered + cancel-in-progress: true + +permissions: + contents: read + +jobs: + ci: + name: Hadolint Dockerfile linting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Lint dockerfiles + id: hadolint + uses: hadolint/hadolint-action@v3.1.0 + with: + recursive: true diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml deleted file mode 100644 index 3f53646..0000000 --- a/.github/workflows/docker-image.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Docker Image CI - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -jobs: - - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Build the Docker image - run: docker build . --file Dockerfile --tag my-image-name:$(date +%s) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..8caf299 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,140 @@ +name: Docker Image Build and Publish + +on: + push: + branches: + - main + schedule: + # At 20:00 UTC everyday (roughly 06:00 AEST) + - cron: '0 20 * * *' + workflow_dispatch: + inputs: + dockerfile_dir: + type: choice + description: Docker Image to Build + options: + - debug-image + - ruby-image + +defaults: + run: + shell: bash + +env: + DOCKER_REPOSITORY: ${{ github.repository }} + DOCKER_REGISTRY: 'ghcr.io' + + +jobs: + matrix_prep: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set_matrix.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + - name: Matrix Setup + id: set_matrix + run: | + ruby .pipeline/generate_matrix.rb -e "${{ github.event_name }}" -d "${{ github.event.inputs.dockerfile_dir }}" + + build: + runs-on: ubuntu-latest + needs: matrix_prep + permissions: + contents: read + packages: write + id-token: write + strategy: + matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}} + fail-fast: false + env: + DOCKERFILE_DIR: ${{ matrix.dockerfile_dir }} + BUILD_ARGS: ${{ matrix.build_args }} + NO_CACHE: false + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - uses: actions/checkout@v4 + - name: Build the Docker image + run: docker build . --file Dockerfile --tag my-image-name:$(date +%s) + - name: Set build cache + run: | + # Never use cache if debug image to force patching. + if [[ "${{ env.DOCKERFILE_DIR }}" == "debug-image" ]]; then + echo "Setting NO_CACHE to true" + echo "NO_CACHE=true" >> $GITHUB_ENV + else + echo "Setting NO_CACHE to false" + echo "NO_CACHE=false" >> $GITHUB_ENV + fi + + - name: Log into registry ${{ env.DOCKER_REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKER_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Set build arg tag version + - name: Set image tags based on build args. + run: | + export BUILD_ARGS="${{ env.BUILD_ARGS }}" + if [[ -z ${BUILD_ARGS} ]]; then + echo "BUILD_ARGS is empty" + APP_NAME="${{ matrix.dockerfile_dir }}" + export APP_NAME + APP_VERSION="${{ matrix.dockerfile_dir }}" + export APP_VERSION + else + export APP_VERSION="${BUILD_ARGS#*=}" + APP_NAME=${BUILD_ARGS%%=*} + fi + echo "The APP_VERSION is '${APP_VERSION}'" + echo "APP_VERSION=${APP_VERSION}" >> $GITHUB_ENV + echo "The APP_NAME is '${APP_NAME}'" + echo "APP_NAME=${APP_NAME}" >> $GITHUB_ENV + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + # uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + uses: docker/metadata-action@v4.6.0 + with: + # list of Docker images to use as base name for tags + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + # generate Docker tags based on the following events/attributes + tags: | + type=raw,value={{date 'YYYY.MM.DD.hhmm' tz='Australia/Brisbane'}} + type=ref,event=branch + type=sha,priority=100,prefix=sha-,suffix=-shrt,format=short + type=sha,format=long,prefix=,priority=9999 + type=raw,value=${{ env.APP_NAME }}-${{ env.APP_VERSION }} + type=raw,value=${{ github.ref_name }}-${{ env.APP_VERSION }} + type=raw,value=${{ env.APP_VERSION }} + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push ${{ env.APP_NAME }} Docker image 🐳 + id: build-and-push + uses: docker/build-push-action@v5 + with: + context: . + file: ./${{ env.DOCKERFILE_DIR }}/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: ${{ env.BUILD_ARGS }} + no-cache: ${{ env.NO_CACHE }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Annotate job with docker pull commands + run: | + tags=$(echo $DOCKER_METADATA_OUTPUT_JSON| jq -r '.tags[]') + for tag in $tags + do + echo "docker pull $tag" >> $GITHUB_STEP_SUMMARY + done diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5058db2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +github_output.log diff --git a/.pipeline/generate_matrix.rb b/.pipeline/generate_matrix.rb new file mode 100644 index 0000000..a1b1caa --- /dev/null +++ b/.pipeline/generate_matrix.rb @@ -0,0 +1,119 @@ +require 'json' +require 'optparse' +require 'net/http' +require 'time' + +# Gets latest ruby versions from endoflife.date +# +# @return [Array] The current supported ruby versions. +def supported_ruby_versions + # Define the API URL + url = 'https://endoflife.date/api/ruby.json' + + # Fetch the data from the API + uri = URI(url) + response = Net::HTTP.get(uri) + + # Parse the JSON response + ruby_versions = JSON.parse(response) + latest_versions = [] + # Extract and display the supported versions + supported_versions = ruby_versions.select { |version| version['eol'] > Time.now.iso8601 } + supported_versions.each do |version| + puts "Ruby #{version['cycle']} - Latest Version: #{version['latest']}, EOL: #{version['eol']}" + latest_versions << version['latest'] + end + latest_versions +end + + +# Generates a matrix of ruby base images to build +# +# @param dockerfile_dir [String] the subdirectory where the dockerfile is located. +# @return [Hash] the github build matrix +def generate_ruby_matrix(dockerfile_dir) + supported_ruby_versions.map do |version| + {"dockerfile_dir" => dockerfile_dir, "build_args" => "RUBY_VERSION=#{version}"} + end +end + +# Generates a matrix of images build on a schedule +# +# @return [Hash] the github build matrix +def schedule_matrix + {"include" => generate_ruby_matrix("ruby-image")} +end + +# Generates a matrix from a workflow dispatch event +# +# @param dockerfile_dir [String] the subdirectory where the dockerfile is located. +# @return [Hash] the github build matrix +def workflow_dispatch_matrix(dockerfile_dir) + case dockerfile_dir + when 'ruby-image' + {"include" => generate_ruby_matrix(dockerfile_dir)} + else + {"include" => [{"dockerfile_dir" => dockerfile_dir}]} + end +end + +# Generates the default build matrix +# +# @return [Hash] the github build matrix +def default_matrix + { + "include" => [ + {"dockerfile_dir" => "debug-image"}, + *generate_ruby_matrix("ruby-image") + ] + } +end + +# Generates a matrix from a workflow dispatch event +# +# @param dockerfile_dir [String] the subdirectory where the dockerfile is located. +# @return [Hash] the github build matrix +def set_matrix(event_name, dockerfile_dir = nil) + puts "Building matrix for event name #{event_name} and dockerfile in #{dockerfile_dir}" + matrix = case event_name + when 'schedule' + puts 'Running schedule event' + schedule_matrix + when 'workflow_dispatch' + puts 'Running workflow_dispatch event' + raise 'Pease specify a dockerfile_dir' if dockerfile_dir.nil? || dockerfile_dir.empty? + workflow_dispatch_matrix(dockerfile_dir) + else + puts 'Running default_matrix event' + default_matrix + end + + puts "matrix=#{matrix.to_json}" + matrix +end + +options = {} +OptionParser.new do |opts| + opts.banner = "Usage: script.rb [options]" + + opts.on("-e", "--event_name EVENT_NAME", "Event name") do |e| + options[:event_name] = e + end + + opts.on("-d", "--dockerfile_dir DOCKERFILE_DIR", "Dockerfile directory") do |d| + options[:dockerfile_dir] = d + end +end.parse! + +event_name = options[:event_name] || ENV['GITHUB_EVENT_NAME'] +dockerfile_dir = options[:dockerfile_dir] || ENV['GITHUB_EVENT_INPUTS_DOCKERFILE_DIR'] +matrix = set_matrix(event_name, dockerfile_dir) + +puts "::group::Job matrix" +puts JSON.pretty_generate(matrix) +puts "::endgroup::" + +# Set GitHub Action output +File.open(ENV['GITHUB_OUTPUT'], 'a') do |file| + file.puts("matrix=#{matrix.to_json}") +end diff --git a/debug-image/Dockerfile b/debug-image/Dockerfile index d09d38d..4754c6a 100644 --- a/debug-image/Dockerfile +++ b/debug-image/Dockerfile @@ -68,14 +68,11 @@ RUN go install github.com/mikefarah/yq/v4@latest # Instal mongo lcient RUN wget -qO- https://www.mongodb.org/static/pgp/server-7.0.asc \ - | tee /etc/apt/trusted.gpg.d/server-7.0.asc \&& + | tee /etc/apt/trusted.gpg.d/server-7.0.asc && \ echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" \ - | tee /etc/apt/sources.list.d/mongodb-org-7.0.list \&& + | tee /etc/apt/sources.list.d/mongodb-org-7.0.list && \ apt-get update -y && apt-get install -y mongodb-mongosh -apt-get update -apt-get install -y mongodb-mongosh - RUN apt-get clean -qq -y && \ apt-get autoclean -qq -y && \ apt-get autoremove -qq -y && \ diff --git a/ruby-image/Dockerfile b/ruby-image/Dockerfile new file mode 100644 index 0000000..7c752e5 --- /dev/null +++ b/ruby-image/Dockerfile @@ -0,0 +1,10 @@ +# syntax = docker/dockerfile:1 + +# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile +ARG RUBY_VERSION +FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base + +# Set environment variables +ENV APP_HOME /app +WORKDIR $APP_HOME +