diff --git a/.forgejo/workflows/build-deploy.yml b/.forgejo/workflows/build-deploy.yml index ea22a9ba..c44c6ca9 100644 --- a/.forgejo/workflows/build-deploy.yml +++ b/.forgejo/workflows/build-deploy.yml @@ -1,4 +1,4 @@ -name: Build and Deploy +name: Build on: push: @@ -33,10 +33,13 @@ jobs: - name: Extract metadata id: meta run: | - echo "image=${{ secrets.REGISTRY_URL }}/${{ github.repository }}" >> $GITHUB_OUTPUT - echo "sha_tag=${{ secrets.REGISTRY_URL }}/${{ github.repository }}:${{ github.sha }}" >> $GITHUB_OUTPUT - echo "latest_tag=${{ secrets.REGISTRY_URL }}/${{ github.repository }}:latest" >> $GITHUB_OUTPUT - echo "cache_tag=${{ secrets.REGISTRY_URL }}/${{ github.repository }}:buildcache" >> $GITHUB_OUTPUT + TIMESTAMP=$(date +%s) + SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) + IMAGE=${{ secrets.REGISTRY_URL }}/${{ github.repository }} + echo "image=${IMAGE}" >> $GITHUB_OUTPUT + echo "tag=${IMAGE}:main-${TIMESTAMP}-${SHORT_SHA}" >> $GITHUB_OUTPUT + echo "latest_tag=${IMAGE}:latest" >> $GITHUB_OUTPUT + echo "cache_tag=${IMAGE}:buildcache" >> $GITHUB_OUTPUT - name: Build and push Docker image uses: docker/build-push-action@v6 @@ -45,41 +48,7 @@ jobs: file: k8s/Dockerfile push: true tags: | - ${{ steps.meta.outputs.sha_tag }} + ${{ steps.meta.outputs.tag }} ${{ steps.meta.outputs.latest_tag }} cache-from: type=registry,ref=${{ steps.meta.outputs.cache_tag }} cache-to: type=registry,ref=${{ steps.meta.outputs.cache_tag }},mode=max - - deploy: - runs-on: ubuntu-latest - needs: build - environment: - name: production - steps: - - name: Set up kubectl - run: | - # Verify kubectl is available - kubectl version --client - - - name: Configure kubectl - run: | - # Configure kubectl context - kubectl config get-contexts - kubectl config use-context towerops/towerops:home-cluster-agent - - - name: Deploy to Kubernetes - run: | - # Set deployment timestamp (ISO 8601 format in UTC) - DEPLOY_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - echo "Deploying at $DEPLOY_TIMESTAMP" - - # Deploy new version (migrations run on app start) - kubectl set image deployment/towerops \ - towerops=${{ secrets.REGISTRY_URL }}/${{ github.repository }}:${{ github.sha }} \ - -n towerops - - kubectl set env deployment/towerops \ - DEPLOY_TIMESTAMP=$DEPLOY_TIMESTAMP \ - -n towerops - - echo "Deployment initiated. Kubernetes will handle rollout asynchronously." diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 87d896df..173daa95 120000 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1 +1 @@ -/nix/store/wvfi88hcjfw2fwbvdmcf1ahm8bfr6awr-pre-commit-config.json \ No newline at end of file +/nix/store/zx636v118rzzqzwafgrw2aybh7l0szrl-pre-commit-config.json \ No newline at end of file diff --git a/flux/git-repository.yaml b/flux/git-repository.yaml new file mode 100644 index 00000000..090d6d26 --- /dev/null +++ b/flux/git-repository.yaml @@ -0,0 +1,12 @@ +apiVersion: source.toolkit.fluxcd.io/v1 +kind: GitRepository +metadata: + name: towerops-web + namespace: flux-system +spec: + interval: 1m + url: https://git.mcintire.me/graham/towerops-web.git + ref: + branch: main + secretRef: + name: forgejo-git-credentials diff --git a/flux/image-policy.yaml b/flux/image-policy.yaml new file mode 100644 index 00000000..44ff2d85 --- /dev/null +++ b/flux/image-policy.yaml @@ -0,0 +1,14 @@ +apiVersion: image.toolkit.fluxcd.io/v1 +kind: ImagePolicy +metadata: + name: towerops-web + namespace: flux-system +spec: + imageRepositoryRef: + name: towerops-web + filterTags: + pattern: '^main-(?P[0-9]+)-[a-f0-9]+$' + extract: '$ts' + policy: + numerical: + order: asc diff --git a/flux/image-repository.yaml b/flux/image-repository.yaml new file mode 100644 index 00000000..fcfd99ef --- /dev/null +++ b/flux/image-repository.yaml @@ -0,0 +1,10 @@ +apiVersion: image.toolkit.fluxcd.io/v1 +kind: ImageRepository +metadata: + name: towerops-web + namespace: flux-system +spec: + image: git.mcintire.me/graham/towerops-web + interval: 1m + secretRef: + name: forgejo-registry diff --git a/flux/image-update-automation.yaml b/flux/image-update-automation.yaml new file mode 100644 index 00000000..3824f6f8 --- /dev/null +++ b/flux/image-update-automation.yaml @@ -0,0 +1,24 @@ +apiVersion: image.toolkit.fluxcd.io/v1 +kind: ImageUpdateAutomation +metadata: + name: towerops-web + namespace: flux-system +spec: + interval: 1m + sourceRef: + kind: GitRepository + name: towerops-web + git: + checkout: + ref: + branch: main + commit: + author: + name: FluxCD + email: fluxcd@towerops.net + messageTemplate: 'chore: update towerops image to {{range .Changed.Changes}}{{.NewValue}}{{end}}' + push: + branch: main + update: + path: ./k8s + strategy: Setters diff --git a/flux/kustomization.yaml b/flux/kustomization.yaml new file mode 100644 index 00000000..c310815d --- /dev/null +++ b/flux/kustomization.yaml @@ -0,0 +1,13 @@ +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: towerops-app + namespace: flux-system +spec: + interval: 5m + sourceRef: + kind: GitRepository + name: towerops-web + path: ./k8s + prune: true + targetNamespace: towerops diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index c1883775..62adfe13 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -24,7 +24,7 @@ spec: priorityClassName: system-cluster-critical terminationGracePeriodSeconds: 90 # Allow 90s for graceful shutdown and connection draining imagePullSecrets: - - name: gitlab-registry + - name: forgejo-registry securityContext: runAsNonRoot: true runAsUser: 65534 @@ -33,8 +33,8 @@ spec: type: RuntimeDefault containers: - name: towerops - image: registry.gitlab.com/towerops/towerops:latest - imagePullPolicy: Always + image: git.mcintire.me/graham/towerops-web:latest # {"$imagepolicy": "flux-system:towerops-web"} + imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false runAsNonRoot: true @@ -75,8 +75,6 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - - name: DEPLOY_TIMESTAMP - value: "1970-01-01T00:00:00Z" # Placeholder, updated by GitLab CI on deploy - name: ETCD_ENABLED value: "true" - name: RELEASE_DISTRIBUTION diff --git a/lib/towerops/preseem.ex b/lib/towerops/preseem.ex index efe0c8d7..79067d50 100644 --- a/lib/towerops/preseem.ex +++ b/lib/towerops/preseem.ex @@ -12,7 +12,10 @@ defmodule Towerops.Preseem do @doc "Get the Preseem AP linked to a specific device, or nil." def get_access_point_for_device(device_id) do - Repo.get_by(AccessPoint, device_id: device_id) + AccessPoint + |> where(device_id: ^device_id) + |> limit(1) + |> Repo.one() end @doc "List all Preseem APs for an organization." diff --git a/test/towerops/preseem_test.exs b/test/towerops/preseem_test.exs index 491effea..3598c36b 100644 --- a/test/towerops/preseem_test.exs +++ b/test/towerops/preseem_test.exs @@ -61,6 +61,25 @@ defmodule Towerops.PreseemTest do result = Preseem.get_access_point_for_device(device.id) assert result.id == ap.id end + + test "returns one AP when multiple are linked to same device", %{organization: org} do + device = device_fixture(%{organization_id: org.id}) + + _ap1 = + insert_access_point!(org, %{ + device_id: device.id, + match_confidence: "auto_ip" + }) + + _ap2 = + insert_access_point!(org, %{ + device_id: device.id, + match_confidence: "auto_mac" + }) + + result = Preseem.get_access_point_for_device(device.id) + assert %AccessPoint{} = result + end end describe "list_access_points/1" do