Repository: rancher/fleet
Pull request #2208 (2024-03-05 09:20:36)

Update integration tests dependencies
Updates `github.com/testcontainers/testcontainers-go` from `v0.26.0` to `v0.28.0`, along with other dependencies.
This should reduce flakiness in integration tests.

I have run integration tests 100 times in a row on my machine, with a single failure.


Patch:
diff --git a/.drone.yml b/.drone.yml
deleted file mode 100644
index e53cce39..00000000
--- a/.drone.yml
+++ /dev/null
@@ -1,418 +0,0 @@
----
-
-kind: pipeline
-type: docker
-name: linux-amd64
-platform:
-  os: linux
-  arch: amd64
-steps:
-- name: build
-  image: rancher/dapper:v0.5.8
-  environment:
-    CROSS: "windows"
-  commands:
-  - dapper ci
-  volumes:
-  - name: docker
-    path: /var/run/docker.sock
-- name: fossa
-  image: registry.suse.com/suse/sle15:15.3
-  failure: ignore
-  environment:
-    FOSSA_API_KEY:
-      from_secret: FOSSA_API_KEY
-  commands:
-    - zypper -n install curl unzip tar
-    - "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install-latest.sh | bash"
-    - fossa analyze
-    - fossa test
-  when:
-    instance:
-      - drone-publish.rancher.io
-    ref:
-      include:
-        - "refs/heads/master"
-    event:
-      - push
-      - tag
-- name: github_binary_release
-  image: plugins/github-release
-  settings:
-    api_key:
-      from_secret: github_token
-    prerelease: true
-    checksum:
-    - sha256
-    checksum_file: CHECKSUMsum-amd64.txt
-    checksum_flatten: true
-    files:
-    - "dist/artifacts/*"
-  when:
-    instance:
-    - drone-publish.rancher.io
-    ref:
-      include:
-      - refs/head/master
-      - refs/tags/*
-      exclude:
-      - refs/tags/*-debug-*
-    event:
-    - tag
-- name: upload-tag
-  pull: default
-  image: plugins/gcs
-  settings:
-    acl:
-      - allUsers:READER
-    cache_control: "no-cache,must-revalidate"
-    source: ./dist/artifacts
-    target: releases.rancher.com/fleet/${DRONE_TAG}
-    token:
-      from_secret: google_auth_key
-  when:
-    event:
-      - tag
-- name: upload-latest
-  pull: default
-  image: plugins/gcs
-  settings:
-    acl:
-      - allUsers:READER
-    cache_control: "no-cache,must-revalidate"
-    source: ./dist/artifacts
-    target: releases.rancher.com/fleet/latest
-    token:
-      from_secret: google_auth_key
-  when:
-    event:
-      - tag
-- name: docker-publish-agent
-  image: plugins/docker
-  settings:
-    dockerfile: package/Dockerfile.agent
-    password:
-      from_secret: docker_password
-    repo: "rancher/fleet-agent"
-    tag: "${DRONE_TAG}-linux-amd64"
-    username:
-      from_secret: docker_username
-    build_args:
-      - "ARCH=amd64"
-  when:
-    instance:
-    - drone-publish.rancher.io
-    ref:
-    - refs/head/master
-    - refs/tags/*
-    event:
-    - tag
-- name: docker-publish-gitjob
-  image: plugins/docker
-  settings:
-    dockerfile: package/Dockerfile.gitjob
-    password:
-      from_secret: docker_password
-    repo: "rancher/gitjob"
-    tag: "${DRONE_TAG}-linux-amd64"
-    username:
-      from_secret: docker_username
-    build_args:
-      - "ARCH=amd64"
-  when:
-    instance:
-    - drone-publish.rancher.io
-    ref:
-    - refs/head/master
-    - refs/tags/*
-    event:
-    - tag
-- name: docker-publish
-  image: plugins/docker
-  settings:
-    dockerfile: package/Dockerfile
-    password:
-      from_secret: docker_password
-    repo: "rancher/fleet"
-    tag: "${DRONE_TAG}-linux-amd64"
-    username:
-      from_secret: docker_username
-    build_args:
-      - "ARCH=amd64"
-  when:
-    instance:
-    - drone-publish.rancher.io
-    ref:
-    - refs/head/master
-    - refs/tags/*
-    event:
-    - tag
-volumes:
-- name: docker
-  host:
-    path: /var/run/docker.sock
-
----
-
-kind: pipeline
-type: docker
-name: linux-arm64
-platform:
-  os: linux
-  arch: arm64
-steps:
-- name: build
-  image: rancher/dapper:v0.5.8
-  commands:
-  - dapper build
-  volumes:
-  - name: docker
-    path: /var/run/docker.sock
-- name: github_binary_release
-  image: plugins/github-release
-  settings:
-    api_key:
-      from_secret: github_token
-    prerelease: true
-    checksum:
-    - sha256
-    checksum_file: CHECKSUMsum-arm64.txt
-    checksum_flatten: true
-    files:
-    - "dist/artifacts/*"
-  when:
-    instance:
-    - drone-publish.rancher.io
-    ref:
-      include:
-      - refs/head/master
-      - refs/tags/*
-      exclude:
-      - refs/tags/*-debug-*
-    event:
-    - tag
-- name: docker-publish-agent
-  image: plugins/docker
-  settings:
-    dockerfile: package/Dockerfile.agent
-    password:
-      from_secret: docker_password
-    repo: "rancher/fleet-agent"
-    tag: "${DRONE_TAG}-linux-arm64"
-    username:
-      from_secret: docker_username
-    build_args:
-      - "ARCH=arm64"
-  when:
-    instance:
-    - drone-publish.rancher.io
-    ref:
-    - refs/head/master
-    - refs/tags/*
-    event:
-    - tag
-- name: docker-publish-gitjob
-  image: plugins/docker
-  settings:
-    dockerfile: package/Dockerfile.gitjob
-    password:
-      from_secret: docker_password
-    repo: "rancher/gitjob"
-    tag: "${DRONE_TAG}-linux-arm64"
-    username:
-      from_secret: docker_username
-    build_args:
-      - "ARCH=arm64"
-  when:
-    instance:
-    - drone-publish.rancher.io
-    ref:
-    - refs/head/master
-    - refs/tags/*
-    event:
-    - tag
-- name: docker-publish
-  image: plugins/docker
-  settings:
-    dockerfile: package/Dockerfile
-    password:
-      from_secret: docker_password
-    repo: "rancher/fleet"
-    tag: "${DRONE_TAG}-linux-arm64"
-    username:
-      from_secret: docker_username
-    build_args:
-      - "ARCH=arm64"
-  when:
-    instance:
-    - drone-publish.rancher.io
-    ref:
-    - refs/head/master
-    - refs/tags/*
-    event:
-    - tag
-volumes:
-- name: docker
-  host:
-    path: /var/run/docker.sock
-
----
-
-kind: pipeline
-type: docker
-name: windows-1809
-platform:
-  os: windows
-  arch: amd64
-  version: 1809
-# Currently have to define "depth" as otherwise clone fails at
-# https://github.com/drone/drone-git/blob/39d233b3d9eccc68e66508a06a725a2567f33143/windows/clone-tag.ps1#L12
-clone:
-  depth: 20
-steps:
-- name: docker-publish-agent
-  image: plugins/docker:windows-1809-amd64
-  settings:
-    purge: false
-    dockerfile: package/Dockerfile-windows.agent
-    password:
-      from_secret: docker_password
-    repo: "rancher/fleet-agent"
-    tag: "${DRONE_TAG}-windows-1809"
-    username:
-      from_secret: docker_username
-    build_args:
-      - "SERVERCORE_VERSION=1809"
-      - "RELEASES=releases.rancher.com"
-      - "VERSION=${DRONE_TAG}"
-    context: package/
-    custom_dns: 1.1.1.1
-  volumes:
-    - name: docker
-      path: \\\\.\\pipe\\docker_engine
-  when:
-    instance:
-    - drone-publish.rancher.io
-    ref:
-    - refs/head/master
-    - refs/tags/*
-    event:
-    - tag
-volumes:
-  - name: docker
-    host:
-      path: \\\\.\\pipe\\docker_engine
-depends_on:
-- linux-amd64
-
----
-
-kind: pipeline
-type: docker
-name: windows-2022
-platform:
-  os: windows
-  arch: amd64
-  version: 2022
-# Currently have to define "depth" as otherwise clone fails at
-# https://github.com/drone/drone-git/blob/39d233b3d9eccc68e66508a06a725a2567f33143/windows/clone-tag.ps1#L12
-clone:
-  depth: 20
-steps:
-  - name: docker-publish-agent
-    image: rancher/drone-images:docker-amd64-ltsc2022
-    settings:
-      dockerfile: package/Dockerfile-windows.agent
-      password:
-        from_secret: docker_password
-      repo: "rancher/fleet-agent"
-      tag: "${DRONE_TAG}-windows-ltsc2022"
-      username:
-        from_secret: docker_username
-      build_args:
-        - "SERVERCORE_VERSION=ltsc2022"
-        - "RELEASES=releases.rancher.com"
-        - "VERSION=${DRONE_TAG}"
-        - ARCH=amd64
-      context: package/
-      custom_dns: 1.1.1.1
-    volumes:
-      - name: docker
-        path: \\\\.\\pipe\\docker_engine
-    when:
-      instance:
-        - drone-publish.rancher.io
-      ref:
-        - refs/head/master
-        - refs/tags/*
-      event:
-        - tag
-volumes:
-  - name: docker
-    host:
-      path: \\\\.\\pipe\\docker_engine
-depends_on:
-  - linux-amd64
-
----
-
-kind: pipeline
-type: docker
-name: manifest
-platform:
-  os: linux
-  arch: amd64
-steps:
-- name: manifest
-  image: plugins/manifest:1.0.2
-  settings:
-    username:
-      from_secret: docker_username
-    password:
-      from_secret: docker_password
-    spec: manifest.tmpl
-  when:
-    instance:
-    - drone-publish.rancher.io
-    ref:
-    - refs/head/master
-    - refs/tags/*
-    event:
-    - tag
-- name: manifest-gitjob
-  image: plugins/manifest:1.0.2
-  settings:
-    username:
-      from_secret: docker_username
-    password:
-      from_secret: docker_password
-    spec: manifest-gitjob.tmpl
-  when:
-    instance:
-    - drone-publish.rancher.io
-    ref:
-    - refs/head/master
-    - refs/tags/*
-    event:
-    - tag
-- name: manifest-agent
-  image: plugins/manifest:1.0.2
-  settings:
-    username:
-      from_secret: docker_username
-    password:
-      from_secret: docker_password
-    spec: manifest-agent.tmpl
-  when:
-    instance:
-    - drone-publish.rancher.io
-    ref:
-    - refs/head/master
-    - refs/tags/*
-    event:
-    - tag
-depends_on:
-- linux-amd64
-- linux-arm64
-- windows-1809
-- windows-2022
diff --git a/.github/workflows/rancher-integration.yml b/.github/workflows/rancher-integration.yml
index e28973ac..7f7dea94 100644
--- a/.github/workflows/rancher-integration.yml
+++ b/.github/workflows/rancher-integration.yml
@@ -18,7 +18,6 @@ on:
   push:
     tags: [ 'v*' ]
     paths-ignore:
-      - 'scripts/**'
       - '*.md'
 
 env:
diff --git a/.github/workflows/release-against-rancher.yml b/.github/workflows/release-against-rancher.yml
index 4ad0e61c..22c9a908 100644
--- a/.github/workflows/release-against-rancher.yml
+++ b/.github/workflows/release-against-rancher.yml
@@ -9,7 +9,7 @@ on:
       rancher_ref:
         description: "Submit PR against the following rancher/rancher branch (e.g. release/v2.7)"
         required: true
-        default: "release/v2.8"
+        default: "release/v2.9"
       new_fleet:
         description: "New Fleet version (e.g. 0.8.0-rc.3)"
         required: true
@@ -22,11 +22,15 @@ on:
         description: "Should the Fleet api be bumped in the Rancher repo? (In most cases true unless there are incompatibilities.)"
         required: true
         default: "true"
+      go_version:
+        description: "Go version used for bumping the api. This should be the same version as in the go.mod file of the project."
+        required: true
+        default: '1.21.*'
 
 env:
   GOARCH: amd64
   CGO_ENABLED: 0
-  SETUP_GO_VERSION: '1.22.*'
+  SETUP_GO_VERSION: ${{github.event.inputs.go_version}}
 
 jobs:
   create-rancher-pr:
diff --git a/.github/workflows/release-fleet.yml b/.github/workflows/release-fleet.yml
new file mode 100644
index 00000000..77dde145
--- /dev/null
+++ b/.github/workflows/release-fleet.yml
@@ -0,0 +1,110 @@
+# Fleet release workflow
+name: Fleet release
+
+on:
+  push:
+    tags:
+      - v**
+
+permissions:
+  contents: write
+  packages: write
+
+env:
+  GOARCH: amd64
+  CGO_ENABLED: 0
+  SETUP_K3D_VERSION: 'v5.5.1'
+
+jobs:
+  build-fleet:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out Fleet
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+
+      - name: Set up Go
+        uses: actions/setup-go@v5
+        with:
+          go-version-file: 'go.mod'
+          check-latest: true
+
+      - name: Run golangci-lint
+        uses: golangci/golangci-lint-action@v4.0.0
+        with:
+          # Required: the version of golangci-lint is required and must be specified without patch version: we always
+          # use the latest patch version.
+          version: v1.55
+          args: --timeout=10m --config=.golangci.json
+
+      - name: Run FOSSA scan
+        uses: fossas/fossa-action@v1.3.1
+        with:
+          api-key: ${{ secrets.FOSSA_API_KEY }}
+
+      - name: Run FOSSA tests
+        uses: fossas/fossa-action@v1.3.1
+        with:
+          api-key: ${{ secrets.FOSSA_API_KEY }}
+          run-tests: true
+
+      - name: Check for code changes
+        run: |
+          ./.github/scripts/check-for-auto-generated-changes.sh
+          go mod verify
+
+      - name: Run unit tests
+        run: go test -cover -tags=test $(go list ./... | grep -v -e /e2e -e /integrationtests)
+
+      - name: Run integration tests
+        env:
+          SETUP_ENVTEST_VER: v0.0.0-20240115093953-9e6e3b144a69
+          ENVTEST_K8S_VERSION: 1.28
+        run: ./.github/scripts/run-integration-tests.sh
+
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v3
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
+      - name: Log into Container registry
+        uses: docker/login-action@v3
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Run GoReleaser
+        uses: goreleaser/goreleaser-action@v5
+        with:
+          distribution: goreleaser
+          version: v1.24.0
+          args: release --clean --verbose
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Upload charts to release
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          repo: "ghcr.io/rancher/fleet"
+          tag: "${GITHUB_REF_NAME}"
+        run: |
+          sed -i \
+              -e "s@repository:.*@repository: $repo@" \
+              -e "s/tag:.*/tag: ${VERSION}/" \
+              charts/fleet/values.yaml
+
+          sed -i \
+              -e "s@repository:.*@repository: $repo@" \
+              -e "s/tag: dev/tag: ${VERSION}/" \
+              charts/fleet-agent/values.yaml
+
+          helm package --version="$VERSION" --app-version="$VERSION" -d ./dist ./charts/fleet
+          helm package --version="$VERSION" --app-version="$VERSION" -d ./dist ./charts/fleet-crd
+          helm package --version="$VERSION" --app-version="$VERSION" -d ./dist ./charts/fleet-agent
+
+          for f in $(find dist/artifacts/ -name '*.tgz'); do
+            gh release upload $tag $f
+          done
diff --git a/.gitignore b/.gitignore
index 39a25da6..33d19e41 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
 /build
-/.dapper
 /.cache
 /bin
 /dist
diff --git a/.goreleaser.yml b/.goreleaser.yml
new file mode 100644
index 00000000..a3e1e388
--- /dev/null
+++ b/.goreleaser.yml
@@ -0,0 +1,268 @@
+---
+project_name: fleet
+
+before:
+  hooks:
+    - go mod download
+
+archives:
+  - id: fleet-controller
+    format: binary
+    name_template: "{{ .Binary }}"
+    builds:
+      - fleet-controller
+
+  - id: fleet-agent
+    format: binary
+    name_template: "{{ .Binary }}"
+    builds:
+      - fleet-agent
+
+  - id: fleet-gitcloner
+    format: binary
+    name_template: "{{ .Binary }}"
+    builds:
+      - fleet-gitcloner
+
+  - id: fleet-gitjob
+    format: binary
+    name_template: "{{ .Binary }}"
+    builds:
+      - fleet-gitjob
+
+  - id: fleet-cli
+    format: binary
+    name_template: "{{ .Binary }}"
+    builds:
+      - fleet-cli
+
+builds:
+  -
+    id: fleet-controller
+    main: ./cmd/fleetcontroller
+    binary: fleetcontroller-{{ .Os }}-{{ .Arch }}{{ if .Arm }}64{{ end }}
+    no_unique_dist_dir: true
+    # gcflags:
+    #   - all="-N -l"
+    ldflags:
+      - -w -s
+      - -X github.com/rancher/fleet/pkg/version.GitCommit={{ .Commit }}
+      - -X github.com/rancher/fleet/pkg/version.Version={{ .Tag }}
+    targets:
+    - linux_amd64_v1
+    - linux_arm64
+  -
+    id: fleet-agent
+    main: ./cmd/fleetagent
+    binary: fleetagent-{{ .Os }}-{{ .Arch }}{{ if .Arm }}64{{ end }}
+    no_unique_dist_dir: true
+    ldflags:
+      - -w -s
+      - -X github.com/rancher/fleet/pkg/version.GitCommit={{ .Commit }}
+      - -X github.com/rancher/fleet/pkg/version.Version={{ .Tag }}
+    targets:
+    - linux_amd64_v1
+    - linux_arm64
+    - windows_amd64
+  -
+    id: fleet-gitcloner
+    main: ./cmd/gitcloner
+    binary: gitcloner-{{ .Os }}-{{ .Arch }}{{ if .Arm }}64{{ end }}
+    no_unique_dist_dir: true
+    ldflags:
+      - -w -s
+      - -X github.com/rancher/fleet/pkg/version.GitCommit={{ .Commit }}
+      - -X github.com/rancher/fleet/pkg/version.Version={{ .Tag }}
+    targets:
+    - linux_amd64_v1
+    - linux_arm64
+  -
+    id: fleet-gitjob
+    main: ./cmd/gitjob
+    binary: gitjob-{{ .Os }}-{{ .Arch }}{{ if .Arm }}64{{ end }}
+    no_unique_dist_dir: true
+    ldflags:
+      - -w -s
+      - -X github.com/rancher/fleet/pkg/version.GitCommit={{ .Commit }}
+      - -X github.com/rancher/fleet/pkg/version.Version={{ .Tag }}
+    targets:
+    - linux_amd64_v1
+    - linux_arm64
+  -
+    id: fleet-cli
+    main: ./cmd/fleetcli
+    binary: fleet-{{ .Os }}-{{ .Arch }}{{ if .Arm }}64{{ end }}
+    no_unique_dist_dir: true
+    ldflags:
+      - -w -s
+      - -X github.com/rancher/fleet/pkg/version.GitCommit={{ .Commit }}
+      - -X github.com/rancher/fleet/pkg/version.Version={{ .Tag }}
+    targets:
+    - linux_amd64_v1
+    - linux_arm64
+    - windows_amd64
+
+changelog:
+  ## Delegate Changelog to release-drafter
+  disable: false
+  use: github
+
+env:
+  - CGO_ENABLED=0
+
+snapshot:
+  name_template: "{{ .Tag }}-next"
+
+dockers:
+  # fleet-controller images
+  -
+    use: buildx
+
+    # GOOS of the built binaries/packages that should be used.
+    goos: linux
+
+    # GOARCH of the built binaries/packages that should be used.
+    goarch: amd64
+
+    # IDs to filter the binaries/packages.
+    ids:
+    - fleet-controller
+
+    # Templates of the Docker image names.
+    image_templates:
+    - "ghcr.io/rancher/fleet:{{ .Tag }}-linux-amd64"
+
+    # Path to the Dockerfile (from the project root).
+    dockerfile: package/Dockerfile
+
+    # Template of the docker build flags.
+    build_flag_templates:
+    - "--pull"
+    - "--label=org.opencontainers.image.created={{.Date}}"
+    - "--label=org.opencontainers.image.title={{.ProjectName}}"
+    - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+    - "--label=org.opencontainers.image.version={{.Version}}"
+    - "--label=org.opencontainers.image.source=https://github.com/rancher/fleet"
+    - "--build-arg=ARCH=amd64"
+    - "--build-arg=BUILD_ENV=goreleaser"
+    - "--platform=linux/amd64"
+  -
+    use: buildx
+    goos: linux
+    goarch: arm64
+    ids:
+    - fleet-controller
+    image_templates:
+    - "ghcr.io/rancher/fleet:{{ .Tag }}-linux-arm64"
+    dockerfile: package/Dockerfile
+    build_flag_templates:
+    - "--pull"
+    - "--label=org.opencontainers.image.created={{.Date}}"
+    - "--label=org.opencontainers.image.title={{.ProjectName}}"
+    - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+    - "--label=org.opencontainers.image.version={{.Version}}"
+    - "--label=org.opencontainers.image.source=https://github.com/rancher/fleet"
+    - "--build-arg=ARCH=arm64"
+    - "--build-arg=BUILD_ENV=goreleaser"
+    - "--platform=linux/arm64/v8"
+  # fleet-agent images
+  -
+    use: buildx
+    goos: linux
+    goarch: amd64
+    ids:
+    - fleet-agent
+    - fleet-cli
+    image_templates:
+    - "ghcr.io/rancher/fleet-agent:{{ .Tag }}-linux-amd64"
+    dockerfile: package/Dockerfile.agent
+    build_flag_templates:
+    - "--pull"
+    - "--label=org.opencontainers.image.created={{.Date}}"
+    - "--label=org.opencontainers.image.title={{.ProjectName}}"
+    - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+    - "--label=org.opencontainers.image.version={{.Version}}"
+    - "--label=org.opencontainers.image.source=https://github.com/rancher/fleet"
+    - "--build-arg=ARCH=amd64"
+    - "--build-arg=BUILD_ENV=goreleaser"
+    - "--platform=linux/amd64"
+    extra_files: [ "package/log.sh" ]
+  -
+    use: buildx
+    goos: linux
+    goarch: arm64
+    ids:
+    - fleet-agent
+    - fleet-cli
+    image_templates:
+    - "ghcr.io/rancher/fleet-agent:{{ .Tag }}-linux-arm64"
+    dockerfile: package/Dockerfile.agent
+    build_flag_templates:
+    - "--pull"
+    - "--label=org.opencontainers.image.created={{.Date}}"
+    - "--label=org.opencontainers.image.title={{.ProjectName}}"
+    - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+    - "--label=org.opencontainers.image.version={{.Version}}"
+    - "--label=org.opencontainers.image.source=https://github.com/rancher/fleet"
+    - "--build-arg=ARCH=arm64"
+    - "--build-arg=BUILD_ENV=goreleaser"
+    - "--platform=linux/arm64/v8"
+    extra_files: [ "package/log.sh" ]
+  # fleet-gitjob images
+  -
+    use: buildx
+    goos: linux
+    goarch: amd64
+    ids:
+    - fleet-gitcloner
+    - fleet-gitjob
+    image_templates:
+    - "ghcr.io/rancher/fleet-gitjob:{{ .Tag }}-linux-amd64"
+    dockerfile: package/Dockerfile.gitjob
+    build_flag_templates:
+    - "--pull"
+    - "--label=org.opencontainers.image.created={{.Date}}"
+    - "--label=org.opencontainers.image.title={{.ProjectName}}"
+    - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+    - "--label=org.opencontainers.image.version={{.Version}}"
+    - "--label=org.opencontainers.image.source=https://github.com/rancher/fleet"
+    - "--build-arg=ARCH=amd64"
+    - "--build-arg=BUILD_ENV=goreleaser"
+    - "--platform=linux/amd64"
+  -
+    use: buildx
+    goos: linux
+    goarch: arm64
+    ids:
+    - fleet-gitcloner
+    - fleet-gitjob
+    image_templates:
+    - "ghcr.io/rancher/fleet-gitjob:{{ .Tag }}-linux-arm64"
+    dockerfile: package/Dockerfile.gitjob
+    build_flag_templates:
+    - "--pull"
+    - "--label=org.opencontainers.image.created={{.Date}}"
+    - "--label=org.opencontainers.image.title={{.ProjectName}}"
+    - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+    - "--label=org.opencontainers.image.version={{.Version}}"
+    - "--label=org.opencontainers.image.source=https://github.com/rancher/fleet"
+    - "--build-arg=ARCH=arm64"
+    - "--build-arg=BUILD_ENV=goreleaser"
+    - "--platform=linux/arm64/v8"
+
+docker_manifests:
+  # https://goreleaser.com/customization/docker_manifest/
+  - name_template: "ghcr.io/rancher/fleet:{{ .Tag }}"
+    image_templates:
+    - "ghcr.io/rancher/fleet:{{ .Tag }}-linux-amd64"
+    - "ghcr.io/rancher/fleet:{{ .Tag }}-linux-arm64"
+
+  - name_template: "ghcr.io/rancher/fleet-agent:{{ .Tag }}"
+    image_templates:
+    - "ghcr.io/rancher/fleet-agent:{{ .Tag }}-linux-amd64"
+    - "ghcr.io/rancher/fleet-agent:{{ .Tag }}-linux-arm64"
+
+  - name_template: "ghcr.io/rancher/fleet-gitjob:{{ .Tag }}"
+    image_templates:
+    - "ghcr.io/rancher/fleet-gitjob:{{ .Tag }}-linux-amd64"
+    - "ghcr.io/rancher/fleet-gitjob:{{ .Tag }}-linux-arm64"
diff --git a/DEVELOPING.md b/DEVELOPING.md
index d1cffc6d..f4a152fb 100644
--- a/DEVELOPING.md
+++ b/DEVELOPING.md
@@ -63,8 +63,8 @@ Now, let's build and push your `fleet-agent` (`linux-amd64` image by default), i
 
 ```sh
 (
-    go fmt ./...
-    REPO=$AGENT_REPO make agent-dev
+    dev/build-fleet
+    docker tag rancher/fleet-agent:dev $AGENT_REPO/fleet-agent:dev
     docker push $AGENT_REPO/fleet-agent:dev
 )
 ```
diff --git a/Dockerfile.dapper b/Dockerfile.dapper
deleted file mode 100644
index 96e9f912..00000000
--- a/Dockerfile.dapper
+++ /dev/null
@@ -1,20 +0,0 @@
-FROM registry.suse.com/bci/golang:1.22
-
-ARG DAPPER_HOST_ARCH
-ENV ARCH $DAPPER_HOST_ARCH
-
-RUN zypper -n install git docker vim wget
-RUN curl -sL https://get.helm.sh/helm-v3.9.3-linux-${ARCH}.tar.gz | tar xvzf - -C /usr/local/bin --strip-components=1
-
-# Temporarily needed for running cmd/codegen/hack/patch_crd_descriptions.sh
-RUN zypper -n install jq python3-pip && python3 -m pip install yq
-
-ENV DAPPER_ENV REPO TAG DRONE_TAG CROSS DEBUG
-ENV DAPPER_SOURCE /go/src/github.com/rancher/fleet/
-ENV DAPPER_OUTPUT ./bin ./dist
-ENV DAPPER_DOCKER_SOCKET true
-ENV DAPPER_RUN_ARGS "-v fleet-pkg:/go/pkg -v fleet-cache:/root/.cache"
-WORKDIR ${DAPPER_SOURCE}
-
-ENTRYPOINT ["./scripts/entry"]
-CMD ["ci"]
diff --git a/Makefile b/Makefile
deleted file mode 100644
index a0649db7..00000000
--- a/Makefile
+++ /dev/null
@@ -1,15 +0,0 @@
-TARGETS := $(shell ls scripts)
-
-.dapper:
-	@echo Downloading dapper
-	@curl -sL https://releases.rancher.com/dapper/latest/dapper-`uname -s`-`uname -m` > .dapper.tmp
-	@@chmod +x .dapper.tmp
-	@./.dapper.tmp -v
-	@mv .dapper.tmp .dapper
-
-$(TARGETS): .dapper
-	./.dapper $@
-
-.DEFAULT_GOAL := default
-
-.PHONY: $(TARGETS)
diff --git a/scripts/boilerplate.go.txt b/cmd/codegen/boilerplate.go.txt
similarity index 100%
rename from scripts/boilerplate.go.txt
rename to cmd/codegen/boilerplate.go.txt
diff --git a/cmd/codegen/hack/generate_and_sort_crds.sh b/cmd/codegen/hack/generate_and_sort_crds.sh
index 48778d24..0847041e 100644
--- a/cmd/codegen/hack/generate_and_sort_crds.sh
+++ b/cmd/codegen/hack/generate_and_sort_crds.sh
@@ -40,7 +40,7 @@ if ! $CONTROLLERGEN --version | grep -q "${CONTROLLERGEN_VERSION}" ; then
 fi
 
 # Run controller-gen
-${CONTROLLERGEN} object:headerFile=scripts/boilerplate.go.txt,year="$(date +%Y)" paths="./pkg/apis/..."
+${CONTROLLERGEN} object:headerFile=cmd/codegen/boilerplate.go.txt,year="$(date +%Y)" paths="./pkg/apis/..."
 ${CONTROLLERGEN} crd webhook paths="./pkg/apis/..." output:stdout > $CRDS_YAML
 # Sort
-run_yq --slurp --sort-keys --explicit-start --yaml-output -i 'sort_by(.metadata.name)[]' $CRDS_YAML
\ No newline at end of file
+run_yq --slurp --sort-keys --explicit-start --yaml-output -i 'sort_by(.metadata.name)[]' $CRDS_YAML
diff --git a/cmd/codegen/main.go b/cmd/codegen/main.go
index 04a85cbc..c3def293 100644
--- a/cmd/codegen/main.go
+++ b/cmd/codegen/main.go
@@ -17,7 +17,7 @@ func main() {
 	os.Unsetenv("GOPATH")
 	controllergen.Run(args.Options{
 		OutputPackage: "github.com/rancher/fleet/pkg/generated",
-		Boilerplate:   "scripts/boilerplate.go.txt",
+		Boilerplate:   "cmd/codegen/boilerplate.go.txt",
 		Groups: map[string]args.Group{
 			"fleet.cattle.io": {
 				Types: []interface{}{
diff --git a/dev/README.md b/dev/README.md
index 2a67c6ec..c9979034 100644
--- a/dev/README.md
+++ b/dev/README.md
@@ -147,7 +147,7 @@ remedy this. By default, the setup script will use `/usr/bin/helm`.
 
 ## Different Script Folders
 
-Our CIs, dapper/drone and github actions, use a different set of scripts. CI
+Our CIs and github actions use a different set of scripts. CI
 does not reuse dev scripts, however dev scripts may use CI scripts. We want to
 keep CI scripts short, targeted and readable. Dev scripts may change in an
 incompatible way at any day.
diff --git a/go.mod b/go.mod
index 1a32d16f..ad85126f 100644
--- a/go.mod
+++ b/go.mod
@@ -33,11 +33,11 @@ require (
 	github.com/rancher/fleet/pkg/apis v0.0.0-00010101000000-000000000000
 	github.com/rancher/lasso v0.0.0-20230830164424-d684fdeb6f29
 	github.com/rancher/wrangler/v2 v2.1.2
-	github.com/reugn/go-quartz v0.9.0
+	github.com/reugn/go-quartz v0.11.1
 	github.com/sirupsen/logrus v1.9.3
 	github.com/spf13/cobra v1.8.0
 	github.com/stretchr/testify v1.8.4
-	github.com/testcontainers/testcontainers-go v0.26.0
+	github.com/testcontainers/testcontainers-go v0.28.0
 	go.uber.org/mock v0.4.0
 	golang.org/x/crypto v0.19.0
 	golang.org/x/exp v0.0.0-20240222234643-814bf88cf225
@@ -88,18 +88,18 @@ require (
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/chai2010/gettext-go v1.0.2 // indirect
 	github.com/cloudflare/circl v1.3.7 // indirect
-	github.com/containerd/containerd v1.7.11 // indirect
+	github.com/containerd/containerd v1.7.12 // indirect
 	github.com/containerd/log v0.1.0 // indirect
 	github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
 	github.com/cpuguy83/dockercfg v0.3.1 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
 	github.com/cyphar/filepath-securejoin v0.2.4 // indirect
 	github.com/distribution/reference v0.5.0 // indirect
-	github.com/docker/cli v24.0.6+incompatible // indirect
-	github.com/docker/distribution v2.8.2+incompatible // indirect
-	github.com/docker/docker v24.0.7+incompatible // indirect
+	github.com/docker/cli v25.0.1+incompatible // indirect
+	github.com/docker/distribution v2.8.3+incompatible // indirect
+	github.com/docker/docker v25.0.3+incompatible // indirect
 	github.com/docker/docker-credential-helpers v0.7.0 // indirect
-	github.com/docker/go-connections v0.4.0 // indirect
+	github.com/docker/go-connections v0.5.0 // indirect
 	github.com/docker/go-metrics v0.0.1 // indirect
 	github.com/docker/go-units v0.5.0 // indirect
 	github.com/emicklei/go-restful/v3 v3.11.3 // indirect
@@ -171,6 +171,7 @@ require (
 	github.com/moby/patternmatcher v0.6.0 // indirect
 	github.com/moby/spdystream v0.2.0 // indirect
 	github.com/moby/sys/sequential v0.5.0 // indirect
+	github.com/moby/sys/user v0.1.0 // indirect
 	github.com/moby/term v0.5.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
@@ -179,8 +180,7 @@ require (
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
 	github.com/opencontainers/go-digest v1.0.0 // indirect
-	github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
-	github.com/opencontainers/runc v1.1.12 // indirect
+	github.com/opencontainers/image-spec v1.1.0-rc6 // indirect
 	github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
 	github.com/pjbgf/sha1cd v0.3.0 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -194,7 +194,7 @@ require (
 	github.com/rubenv/sql-migrate v1.5.2 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/sergi/go-diff v1.3.1 // indirect
-	github.com/shirou/gopsutil/v3 v3.23.11 // indirect
+	github.com/shirou/gopsutil/v3 v3.23.12 // indirect
 	github.com/shoenig/go-m1cpu v0.1.6 // indirect
 	github.com/shopspring/decimal v1.3.1 // indirect
 	github.com/skeema/knownhosts v1.2.1 // indirect
@@ -243,7 +243,7 @@ require (
 	k8s.io/component-base v0.29.2 // indirect
 	k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 // indirect
 	k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect
-	oras.land/oras-go v1.2.4 // indirect
+	oras.land/oras-go v1.2.5 // indirect
 	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
 	sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 4f896b97..751bb7fb 100644
--- a/go.sum
+++ b/go.sum
@@ -277,8 +277,8 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH
 github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
 github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
-github.com/containerd/containerd v1.7.11 h1:lfGKw3eU35sjV0aG2eYZTiwFEY1pCzxdzicHP3SZILw=
-github.com/containerd/containerd v1.7.11/go.mod h1:5UluHxHTX2rdvYuZ5OJTC5m/KJNs0Zs9wVoJm9zf5ZE=
+github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0=
+github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk=
 github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM=
 github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
 github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
@@ -301,16 +301,16 @@ github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aB
 github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI=
 github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
 github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
-github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY=
-github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
-github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
-github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
-github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
-github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/cli v25.0.1+incompatible h1:mFpqnrS6Hsm3v1k7Wa/BO23oz0k121MTbTO1lpcGSkU=
+github.com/docker/cli v25.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
+github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
+github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
+github.com/docker/docker v25.0.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ=
+github.com/docker/docker v25.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
 github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
-github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
-github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
+github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
+github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
 github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
 github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
 github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
@@ -542,7 +542,10 @@ github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
 github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
 github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
 github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
 github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -669,6 +672,8 @@ github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vyg
 github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
 github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
 github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
+github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
+github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
 github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
 github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -697,10 +702,8 @@ github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
 github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
-github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
-github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
-github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss=
-github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8=
+github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU=
+github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
 github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
 github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
 github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
@@ -745,8 +748,8 @@ github.com/rancher/lasso v0.0.0-20230830164424-d684fdeb6f29 h1:+kige/h8/LnzWgPjB
 github.com/rancher/lasso v0.0.0-20230830164424-d684fdeb6f29/go.mod h1:kgk9kJVMj9FIrrXU0iyM6u/9Je4bEjPImqswkTVaKsQ=
 github.com/rancher/wrangler/v2 v2.1.2 h1:XAiReB3caOAepsTHs12AwKVv4vBJkjLosZPXbbj/85E=
 github.com/rancher/wrangler/v2 v2.1.2/go.mod h1:LgpEqT2ATL0MevUsL5rohrAEU419MIPeswsMNHw35d4=
-github.com/reugn/go-quartz v0.9.0 h1:JPaxyWx6YtNG3020DuT/LXRqishMokWwpMiLfiTWGJg=
-github.com/reugn/go-quartz v0.9.0/go.mod h1:no4ktgYbAAuY0E1SchR8cTx1LF4jYIzdgaQhzRPSkpk=
+github.com/reugn/go-quartz v0.11.1 h1:T67ZeP0h2uRxtoKFZ1Lnbf3u4jh6PBgeBlw2BUk9rW0=
+github.com/reugn/go-quartz v0.11.1/go.mod h1:no4ktgYbAAuY0E1SchR8cTx1LF4jYIzdgaQhzRPSkpk=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
@@ -759,8 +762,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
 github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
-github.com/shirou/gopsutil/v3 v3.23.11 h1:i3jP9NjCPUz7FiZKxlMnODZkdSIp2gnzfrvsu9CuWEQ=
-github.com/shirou/gopsutil/v3 v3.23.11/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
+github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
+github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
 github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
 github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
 github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@@ -799,8 +802,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/testcontainers/testcontainers-go v0.26.0 h1:uqcYdoOHBy1ca7gKODfBd9uTHVK3a7UL848z09MVZ0c=
-github.com/testcontainers/testcontainers-go v0.26.0/go.mod h1:ICriE9bLX5CLxL9OFQ2N+2N+f+803LNJ1utJb1+Inx0=
+github.com/testcontainers/testcontainers-go v0.28.0 h1:1HLm9qm+J5VikzFDYhOd+Zw12NtOl+8drH2E8nTY1r8=
+github.com/testcontainers/testcontainers-go v0.28.0/go.mod h1:COlDpUXbwW3owtpMkEB1zo9gwb1CoKVKlyrVPejF4AU=
 github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
 github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
 github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
@@ -848,11 +851,19 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZ
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q=
 go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
 go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
 go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE=
 go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8=
+go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
+go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
 go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
 go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
+go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
 go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY=
 go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -1483,8 +1494,8 @@ k8s.io/kubernetes v1.29.2 h1:8hh1cntqdulanjQt7wSSSsJfBgOyx6fUdFWslvGL5m0=
 k8s.io/kubernetes v1.29.2/go.mod h1:xZPKU0yO0CBbLTnbd+XGyRmmtmaVuJykDb8gNCkeeUE=
 k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ=
 k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
-oras.land/oras-go v1.2.4 h1:djpBY2/2Cs1PV87GSJlxv4voajVOMZxqqtq9AB8YNvY=
-oras.land/oras-go v1.2.4/go.mod h1:DYcGfb3YF1nKjcezfX2SNlDAeQFKSXmf+qrFmrh4324=
+oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo=
+oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/internal/cmd/controller/imagescan/gitcommit_job.go b/internal/cmd/controller/imagescan/gitcommit_job.go
index 9a7d22b8..d7677005 100644
--- a/internal/cmd/controller/imagescan/gitcommit_job.go
+++ b/internal/cmd/controller/imagescan/gitcommit_job.go
@@ -64,8 +64,8 @@ func gitCommitDescription(namespace string, name string) string {
 	return fmt.Sprintf("gitrepo-git-commit-%s-%s", namespace, name)
 }
 
-func GitCommitKey(namespace string, name string) int {
-	return quartz.HashCode(gitCommitDescription(namespace, name))
+func GitCommitKey(namespace string, name string) *quartz.JobKey {
+	return quartz.NewJobKey(gitCommitDescription(namespace, name))
 }
 
 func NewGitCommitJob(c client.Client, namespace string, name string) *GitCommitJob {
@@ -78,24 +78,22 @@ func NewGitCommitJob(c client.Client, namespace string, name string) *GitCommitJ
 	}
 }
 
-func (j *GitCommitJob) Execute(ctx context.Context) {
+func (j *GitCommitJob) Execute(ctx context.Context) error {
 	if !j.sem.TryAcquire(1) {
 		// already running
-		return
+		return nil
 	}
 	defer j.sem.Release(1)
 
 	j.cloneAndReplace(ctx)
+
+	return nil
 }
 
 func (j *GitCommitJob) Description() string {
 	return gitCommitDescription(j.namespace, j.name)
 }
 
-func (j *GitCommitJob) Key() int {
-	return GitCommitKey(j.namespace, j.name)
-}
-
 func (j *GitCommitJob) cloneAndReplace(ctx context.Context) {
 	logger := log.FromContext(ctx).WithName("imagescan-clone").WithValues("gitrepo", j.name, "namespace", j.namespace)
 	nsn := types.NamespacedName{Namespace: j.namespace, Name: j.name}
diff --git a/internal/cmd/controller/imagescan/tagscan_job.go b/internal/cmd/controller/imagescan/tagscan_job.go
index dcfc309f..9e86a605 100644
--- a/internal/cmd/controller/imagescan/tagscan_job.go
+++ b/internal/cmd/controller/imagescan/tagscan_job.go
@@ -8,6 +8,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"strings"
 	"time"
 
 	"github.com/Masterminds/semver/v3"
@@ -50,8 +51,8 @@ func tagScanDescription(namespace string, name string) string {
 	return fmt.Sprintf("image-tag-scan-%s-%s", namespace, name)
 }
 
-func TagScanKey(namespace string, name string) int {
-	return quartz.HashCode(tagScanDescription(namespace, name))
+func TagScanKey(namespace string, name string) *quartz.JobKey {
+	return quartz.NewJobKey(tagScanDescription(namespace, name))
 }
 
 func NewTagScanJob(c client.Client, namespace string, name string) *TagScanJob {
@@ -64,24 +65,22 @@ func NewTagScanJob(c client.Client, namespace string, name string) *TagScanJob {
 	}
 }
 
-func (j *TagScanJob) Execute(ctx context.Context) {
+func (j *TagScanJob) Execute(ctx context.Context) error {
 	if !j.sem.TryAcquire(1) {
 		// already running
-		return
+		return nil
 	}
 	defer j.sem.Release(1)
 
 	j.updateImageTags(ctx)
+
+	return nil
 }
 
 func (j *TagScanJob) Description() string {
 	return tagScanDescription(j.namespace, j.name)
 }
 
-func (j *TagScanJob) Key() int {
-	return TagScanKey(j.namespace, j.name)
-}
-
 func (j *TagScanJob) updateImageTags(ctx context.Context) {
 	logger := log.FromContext(ctx).WithName("imagescan-tag-scanner")
 	nsn := types.NamespacedName{Namespace: j.namespace, Name: j.name}
@@ -265,7 +264,7 @@ func latestTag(policy fleet.ImagePolicyChoice, versions []string) (string, error
 		if policy.Alphabetical.Order == "" {
 			des = true
 		} else {
-			des = policy.Alphabetical.Order == AlphabeticalOrderDesc
+			des = strings.ToUpper(policy.Alphabetical.Order) == AlphabeticalOrderDesc
 		}
 		var latest string
 		for _, version := range versions {
diff --git a/internal/cmd/controller/reconciler/imagescan_controller.go b/internal/cmd/controller/reconciler/imagescan_controller.go
index 257753f9..db80ff00 100644
--- a/internal/cmd/controller/reconciler/imagescan_controller.go
+++ b/internal/cmd/controller/reconciler/imagescan_controller.go
@@ -58,8 +58,14 @@ func (r *ImageScanReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
 
 	// Make sure no duplicate jobs are scheduled. DeleteJob might return an
 	// error if the job does not exist, which we ignore.
-	_ = r.Scheduler.DeleteJob(imagescan.TagScanKey(req.Namespace, req.Name))
-	err = r.Scheduler.ScheduleJob(ctx, imagescan.NewTagScanJob(r.Client, req.Namespace, req.Name), quartz.NewSimpleTrigger(interval.Duration))
+	tagScanKey := imagescan.TagScanKey(req.Namespace, req.Name)
+	_ = r.Scheduler.DeleteJob(tagScanKey)
+	err = r.Scheduler.ScheduleJob(
+		quartz.NewJobDetail(
+			imagescan.NewTagScanJob(r.Client, req.Namespace, req.Name),
+			tagScanKey),
+		quartz.NewSimpleTrigger(interval.Duration),
+	)
 	if err != nil {
 		logger.Error(err, "Failed to schedule imagescan tagscan job")
 		return ctrl.Result{}, err
@@ -71,8 +77,14 @@ func (r *ImageScanReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
 		return ctrl.Result{}, client.IgnoreNotFound(err)
 	}
 
-	_ = r.Scheduler.DeleteJob(imagescan.GitCommitKey(gitrepo.Namespace, gitrepo.Name))
-	err = r.Scheduler.ScheduleJob(ctx, imagescan.NewGitCommitJob(r.Client, gitrepo.Namespace, gitrepo.Name), quartz.NewSimpleTrigger(interval.Duration))
+	gitCommitKey := imagescan.GitCommitKey(gitrepo.Namespace, gitrepo.Name)
+	_ = r.Scheduler.DeleteJob(gitCommitKey)
+	err = r.Scheduler.ScheduleJob(
+		quartz.NewJobDetail(
+			imagescan.NewGitCommitJob(r.Client, gitrepo.Namespace, gitrepo.Name),
+			gitCommitKey),
+		quartz.NewSimpleTrigger(interval.Duration),
+	)
 	if err != nil {
 		logger.Error(err, "Failed to schedule gitrepo gitcommit job")
 		return ctrl.Result{}, err
diff --git a/package/Dockerfile b/package/Dockerfile
index ae854134..d40ac464 100644
--- a/package/Dockerfile
+++ b/package/Dockerfile
@@ -1,6 +1,7 @@
 ARG BUILD_ENV=dapper
+ARG ARCH
 
-FROM registry.suse.com/bci/bci-busybox:15.5 AS base
+FROM --platform=linux/$ARCH registry.suse.com/bci/bci-busybox:15.5 AS base
 
 FROM base AS copy_dapper
 ONBUILD ARG ARCH
@@ -10,6 +11,10 @@ FROM base AS copy_buildx
 ONBUILD ARG TARGETARCH
 ONBUILD COPY bin/fleetcontroller-linux-${TARGETARCH} /usr/bin/fleetcontroller
 
+FROM base AS copy_goreleaser
+ONBUILD ARG ARCH
+ONBUILD COPY fleetcontroller-linux-${ARCH} /usr/bin/fleetcontroller
+
 FROM copy_${BUILD_ENV}
 RUN addgroup -g 1000 fleet-apply && adduser -u 1000 -G fleet-apply -D fleet-apply
 USER 1000
diff --git a/package/Dockerfile.agent b/package/Dockerfile.agent
index 32a19353..98f89229 100644
--- a/package/Dockerfile.agent
+++ b/package/Dockerfile.agent
@@ -1,6 +1,7 @@
 ARG BUILD_ENV=dapper
+ARG ARCH
 
-FROM registry.suse.com/suse/git:2.35 AS base
+FROM --platform=linux/$ARCH registry.suse.com/suse/git:2.35 AS base
 COPY package/log.sh /usr/bin/
 # Create non-root user and group
 RUN echo "fleet-apply:x:1000:1000::/home/fleet-apply:/bin/bash" >> /etc/passwd && \
@@ -18,6 +19,11 @@ ONBUILD ARG TARGETARCH
 ONBUILD COPY bin/fleetagent-linux-$TARGETARCH /usr/bin/fleetagent
 ONBUILD COPY bin/fleet-linux-$TARGETARCH /usr/bin/fleet
 
+FROM base AS copy_goreleaser
+ONBUILD ARG ARCH
+ONBUILD COPY fleetagent-linux-$ARCH /usr/bin/fleetagent
+ONBUILD COPY fleet-linux-$ARCH /usr/bin/fleet
+
 FROM copy_${BUILD_ENV}
 USER 1000
 CMD ["fleetagent"]
diff --git a/package/Dockerfile.gitjob b/package/Dockerfile.gitjob
index 83b32dcc..2829cc61 100644
--- a/package/Dockerfile.gitjob
+++ b/package/Dockerfile.gitjob
@@ -1,6 +1,7 @@
 ARG BUILD_ENV=dapper
+ARG ARCH
 
-FROM registry.suse.com/bci/bci-base:15.5 AS base
+FROM --platform=linux/$ARCH registry.suse.com/bci/bci-base:15.5 AS base
 RUN zypper -n update && \
     zypper -n install openssh catatonit git-core && \
     zypper -n clean -a
@@ -16,6 +17,11 @@ ONBUILD ARG TARGETARCH
 ONBUILD COPY bin/gitjob-linux-$TARGETARCH /usr/bin/gitjob
 ONBUILD COPY bin/gitcloner-linux-$TARGETARCH /usr/bin/gitcloner
 
+FROM base AS copy_goreleaser
+ONBUILD ARG ARCH
+ONBUILD COPY gitjob-linux-$ARCH /usr/bin/gitjob
+ONBUILD COPY gitcloner-linux-$ARCH /usr/bin/gitcloner
+
 FROM copy_${BUILD_ENV}
 USER 1000
 ENTRYPOINT ["catatonit", "--"]
diff --git a/scripts/agent-dev b/scripts/agent-dev
deleted file mode 100755
index 28a8d701..00000000
--- a/scripts/agent-dev
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-set -e
-
-source $(dirname $0)/version
-
-cd $(dirname $0)/..
-
-mkdir -p bin
-mkdir -p dist/artifacts
-
-LINKFLAGS="-X github.com/rancher/fleet/pkg/version.Version=$VERSION"
-LINKFLAGS="-X github.com/rancher/fleet/pkg/version.GitCommit=$COMMIT $LINKFLAGS"
-LINKFLAGS="-s -w $LINKFLAGS"
-LINKFLAGS="$LINKFLAGS -extldflags -static"
-
-AGENT_ARCH=amd64
-
-echo "Building binary: fleet"
-GOOS=linux GOARCH=$AGENT_ARCH CGO_ENABLED=0 go build -ldflags "$LINKFLAGS" -o bin/fleet-linux-$AGENT_ARCH ./cmd/fleetcli
-cp bin/fleet-linux-$AGENT_ARCH dist/artifacts/
-
-echo "Building binary: fleetagent"
-GOOS=linux GOARCH=$AGENT_ARCH CGO_ENABLED=0 go build -ldflags "$LINKFLAGS" -o bin/fleetagent-linux-$AGENT_ARCH ./cmd/fleetagent
-cp bin/fleetagent-linux-$AGENT_ARCH dist/artifacts/
-
-IMAGE=$REPO/fleet-agent:$TAG
-echo "Building fleet-agent image for linux-$AGENT_ARCH: $IMAGE"
-docker build -f package/Dockerfile.agent -t $IMAGE --build-arg ARCH=$AGENT_ARCH .
diff --git a/scripts/build b/scripts/build
deleted file mode 100755
index d0a25ce0..00000000
--- a/scripts/build
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/bin/bash
-set -e
-
-source $(dirname $0)/version
-
-cd $(dirname $0)/..
-
-mkdir -p bin
-
-if [ -n "${DEBUG}" ]; then
-  GCFLAGS="-N -l"
-fi
-
-LINKFLAGS="-X github.com/rancher/fleet/pkg/version.Version=$VERSION"
-LINKFLAGS="-X github.com/rancher/fleet/pkg/version.GitCommit=$COMMIT $LINKFLAGS"
-if [ -z "${DEBUG}" ]; then
-  LINKFLAGS="-s -w $LINKFLAGS"
-fi
-OTHER_LINKFLAGS="-extldflags -static"
-
-function build-binaries() {
-    printf "Building for ${1}-${2} ...\n"
-    TEMP_LINKFLAGS=$LINKFLAGS
-    if [ $1 = "linux" ]; then
-        TEMP_LINKFLAGS="$LINKFLAGS $OTHER_LINKFLAGS"
-    fi
-    TEMP_SUFFIX=""
-    if [ $1 = "windows" ]; then
-        TEMP_SUFFIX=".exe"
-    fi
-    GOOS=${1} GOARCH=${2} CGO_ENABLED=0 go build -gcflags="all=${GCFLAGS}" -ldflags "$TEMP_LINKFLAGS" -o bin/fleet-${1}-${2}${TEMP_SUFFIX} ./cmd/fleetcli
-    if ! [ $1 = "darwin" ]; then
-        GOOS=${1} GOARCH=${2} CGO_ENABLED=0 go build -gcflags="all=${GCFLAGS}" -ldflags "$TEMP_LINKFLAGS" -o bin/fleetcontroller-${1}-${2}${TEMP_SUFFIX} ./cmd/fleetcontroller
-        GOOS=${1} GOARCH=${2} CGO_ENABLED=0 go build -gcflags="all=${GCFLAGS}" -ldflags "$TEMP_LINKFLAGS" -o bin/fleetagent-${1}-${2}${TEMP_SUFFIX} ./cmd/fleetagent
-        GOOS=${1} GOARCH=${2} CGO_ENABLED=0 go build -gcflags="all=${GCFLAGS}" -ldflags "$TEMP_LINKFLAGS" -o bin/gitjob-${1}-${2}${TEMP_SUFFIX} ./cmd/gitjob
-        GOOS=${1} GOARCH=${2} CGO_ENABLED=0 go build -gcflags="all=${GCFLAGS}" -ldflags "$TEMP_LINKFLAGS" -o bin/gitcloner-${1}-${2}${TEMP_SUFFIX} ./cmd/gitcloner
-    fi
-}
-
-if [ "$CROSS" = "true" ]; then
-    # If local (host) is unset, we default to false since we are already cross-compiling for all platforms.
-    if [ -z "$LOCAL" ]; then
-        LOCAL="false"
-    fi
-    OS_ARCH=( "linux amd64" "linux arm64" "darwin amd64" "windows amd64" )
-    for i in "${OS_ARCH[@]}"; do
-        set -- $i
-        build-binaries ${1} ${2}
-    done
-elif [ "$CROSS" = "windows" ]; then
-   build-binaries "windows" "amd64"
-fi
-
-# Always build for the host (local) OS and ARCH unless otherwise specified (setting LOCAL to "false").
-# LOCAL will be set to "false" if CROSS is "true" (avoids compiling the host OS and ARCH twice).
-if ! [ "$LOCAL" = "false" ]; then
-    build-binaries ${OS} ${ARCH}
-fi
-
-mkdir -p dist/artifacts
-cp -r bin/fleet* dist/artifacts/
diff --git a/scripts/ci b/scripts/ci
deleted file mode 100755
index 52334105..00000000
--- a/scripts/ci
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-set -e
-
-cd $(dirname $0)
-
-./build
-./test
-./validate
-./validate-ci
-./package
diff --git a/scripts/default b/scripts/default
deleted file mode 100755
index af2ad2db..00000000
--- a/scripts/default
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-set -e
-
-cd $(dirname $0)
-
-./build
-./test
-./package
diff --git a/scripts/entry b/scripts/entry
deleted file mode 100755
index 78fb5679..00000000
--- a/scripts/entry
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-set -e
-
-mkdir -p bin dist
-if [ -e ./scripts/$1 ]; then
-    ./scripts/"$@"
-else
-    exec "$@"
-fi
-
-chown -R $DAPPER_UID:$DAPPER_GID .
diff --git a/scripts/package b/scripts/package
deleted file mode 100755
index 2cc1bf9b..00000000
--- a/scripts/package
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-set -e
-
-source $(dirname $0)/version
-
-cd $(dirname $0)/..
-
-function build-image() {
-  IMAGE=${REPO}/${1}:${TAG}
-  DOCKERFILE=package/Dockerfile${2}
-  if [ -e ${DOCKERFILE}.${ARCH} ]; then
-      DOCKERFILE=${DOCKERFILE}.${ARCH}
-  fi
-
-  docker build -f ${DOCKERFILE} -t ${IMAGE} --build-arg ARCH=${ARCH} .
-  echo Built ${IMAGE}
-
-  if [ "$PUSH" = "true" ]; then
-      docker push ${IMAGE}
-  fi
-}
-
-build-image fleet
-build-image fleet-agent .agent
-build-image gitjob .gitjob
-
-./scripts/package-helm
diff --git a/scripts/package-helm b/scripts/package-helm
deleted file mode 100755
index 4ea6d9dc..00000000
--- a/scripts/package-helm
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/bin/bash
-set -e
-
-if ! hash helm 2>/dev/null; then
-    exit 0
-fi
-
-cd $(dirname $0)/..
-. ./scripts/version
-
-rm -rf build/charts
-mkdir -p build dist/artifacts
-cp -rf charts build/
-rm -rf build/charts/fleet-crd/templates/crds.yaml
-
-sed -i \
-    -e 's/^version:.*/version: '${HELM_VERSION}'/' \
-    -e 's/appVersion:.*/appVersion: '${HELM_VERSION}'/' \
-    build/charts/fleet/Chart.yaml
-
-sed -i \
-    -e 's/tag:.*/tag: '${HELM_TAG}'/' \
-    build/charts/fleet/values.yaml
-
-sed -i \
-    -e 's/version:.*/version: '${HELM_VERSION}'/' \
-    -e 's/appVersion:.*/appVersion: '${HELM_VERSION}'/' \
-    build/charts/fleet-crd/Chart.yaml
-
-sed -i \
-    -e 's/version:.*/version: '${HELM_VERSION}'/' \
-    -e 's/appVersion:.*/appVersion: '${HELM_VERSION}'/' \
-    build/charts/fleet-agent/Chart.yaml
-
-sed -i \
-    -e 's/tag: dev/tag: '${HELM_TAG}'/' \
-    build/charts/fleet-agent/values.yaml
-
-bash ./cmd/codegen/hack/generate_and_sort_crds.sh ./build/charts/fleet-crd/templates/crds.yaml
-
-helm package -d ./dist/artifacts ./build/charts/fleet
-helm package -d ./dist/artifacts ./build/charts/fleet-crd
-helm package -d ./dist/artifacts ./build/charts/fleet-agent
diff --git a/scripts/release b/scripts/release
deleted file mode 100755
index 7af0df35..00000000
--- a/scripts/release
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-
-exec $(dirname $0)/ci
diff --git a/scripts/validate b/scripts/validate
deleted file mode 100755
index 555703b2..00000000
--- a/scripts/validate
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-set -e
-
-cd "$(dirname "$0")"/..
-
-echo Running validation
-
-go mod verify
diff --git a/scripts/validate-ci b/scripts/validate-ci
deleted file mode 100755
index e7981cf8..00000000
--- a/scripts/validate-ci
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/bash
-set -e
-
-cd $(dirname $0)/..
-
-go generate
-
-source ./scripts/version
-
-if [ -n "$DIRTY" ]; then
-    echo Git is dirty
-    git status
-    git diff
-    exit 1
-fi
diff --git a/scripts/version b/scripts/version
deleted file mode 100755
index 5fefcd2a..00000000
--- a/scripts/version
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/bin/bash
-
-if [ -n "$(git status --porcelain --untracked-files=no)" ]; then
-    DIRTY="+dirty"
-fi
-
-GIT_TAG=${DRONE_TAG:-$(git tag -l --contains HEAD | head -n 1)}
-
-if [[ -z "$DIRTY" && -n "$GIT_TAG" ]]; then
-    VERSION=$GIT_TAG
-else
-    # Make sure tags are available in the CI environment
-    readarray -t TAGS < <(git tag -l)
-    if [[ ${#TAGS[@]} == 0 ]]; then
-        git fetch -t
-    fi
-    VERSION="$(git describe --tags)${DIRTY}"
-fi
-
-if [ -z "$ARCH" ]; then
-    ARCH=$(go env GOHOSTARCH)
-fi
-
-if [ -z "$OS" ]; then
-    OS=$(go env GOHOSTOS)
-fi
-
-SUFFIX="-${ARCH}"
-
-HELM_TAG=${TAG:-${VERSION}}
-HELM_VERSION=${HELM_TAG/v/}
-TAG=${TAG:-${VERSION}${SUFFIX}}
-REPO=${REPO:-rancher}
-
-if echo $TAG | grep -q dirty; then
-    TAG=dev
-    HELM_TAG=dev
-    HELM_VERSION=0.0.0-dev
-fi


Test patch:
diff --git a/internal/cmd/controller/imagescan/tagscan_job_test.go b/internal/cmd/controller/imagescan/tagscan_job_test.go
new file mode 100644
index 00000000..0a3bdedb
--- /dev/null
+++ b/internal/cmd/controller/imagescan/tagscan_job_test.go
@@ -0,0 +1,58 @@
+package imagescan
+
+import (
+	"testing"
+
+	fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
+)
+
+func TestLatestTag(t *testing.T) {
+	var alphabeticalVersions = []string{"a", "b", "c"}
+
+	tests := []struct {
+		name, want string
+		policy     fleet.ImagePolicyChoice
+	}{
+		{
+			name: "alphabetical asc lowercase",
+			policy: fleet.ImagePolicyChoice{
+				Alphabetical: &fleet.AlphabeticalPolicy{Order: "asc"},
+			},
+			want: "a",
+		},
+		{
+			name: "alphabetical asc uppercase",
+			policy: fleet.ImagePolicyChoice{
+				Alphabetical: &fleet.AlphabeticalPolicy{Order: "ASC"},
+			},
+			want: "a",
+		},
+		{
+			name: "alphabetical desc lowercase",
+			policy: fleet.ImagePolicyChoice{
+				Alphabetical: &fleet.AlphabeticalPolicy{Order: "desc"},
+			},
+			want: "c",
+		},
+		{
+			name: "alphabetical desc uppercase",
+			policy: fleet.ImagePolicyChoice{
+				Alphabetical: &fleet.AlphabeticalPolicy{Order: "DESC"},
+			},
+			want: "c",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := latestTag(tt.policy, alphabeticalVersions)
+			if err != nil {
+				t.Fatalf("Error calling latestTag: %v", err)
+			}
+
+			if got != tt.want {
+				t.Errorf("latestTag() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/scripts/test b/scripts/test
deleted file mode 100755
index c9f3a702..00000000
--- a/scripts/test
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-set -eo pipefail
-
-cd "$(dirname "$0")/.."
-
-echo Running tests
-go test -cover -tags=test $(go list ./... | grep -v -e /e2e -e /integrationtests)