From d54530ac895741f4ca8b14e546522e429bb0561a Mon Sep 17 00:00:00 2001 From: Ilya Semerhanov Date: Tue, 6 May 2025 23:43:16 +0200 Subject: [PATCH 01/62] add sts and cj kinds --- chart/crds/sleepschedule.yaml | 29 ++++++- .../controllers/sleep_schedule_controller.ex | 75 +++++++++++++---- lib/drowzee/k8s.ex | 14 ++++ lib/drowzee/k8s/cron_job.ex | 26 ++++++ lib/drowzee/k8s/sleep_schedule.ex | 82 ++++++++++++++++++- lib/drowzee/k8s/stateful_set.ex | 30 +++++++ 6 files changed, 240 insertions(+), 16 deletions(-) create mode 100644 lib/drowzee/k8s/cron_job.ex create mode 100644 lib/drowzee/k8s/stateful_set.ex diff --git a/chart/crds/sleepschedule.yaml b/chart/crds/sleepschedule.yaml index ab4b2e7..3703b53 100644 --- a/chart/crds/sleepschedule.yaml +++ b/chart/crds/sleepschedule.yaml @@ -53,7 +53,6 @@ spec: - sleepTime - timezone - wakeTime - - deployments properties: deployments: type: array @@ -65,6 +64,26 @@ spec: properties: name: type: string + statefulSets: + type: array + description: The stateful sets that will be slept/woken. + items: + type: object + required: + - name + properties: + name: + type: string + cronJobs: + type: array + description: The cron jobs that will be suspended/resumed. + items: + type: object + required: + - name + properties: + name: + type: string ingressName: type: string description: The ingress that will be slept/woken. @@ -95,6 +114,14 @@ spec: type: string description: Deployments jsonPath: .spec.deployments[*].name + - name: StatefulSets + type: string + description: StatefulSets + jsonPath: .spec.statefulSets[*].name + - name: CronJobs + type: string + description: CronJobs + jsonPath: .spec.cronJobs[*].name - name: Sleeping? type: string description: Current Status diff --git a/lib/drowzee/controllers/sleep_schedule_controller.ex b/lib/drowzee/controllers/sleep_schedule_controller.ex index 3d26be5..86b3217 100644 --- a/lib/drowzee/controllers/sleep_schedule_controller.ex +++ b/lib/drowzee/controllers/sleep_schedule_controller.ex @@ -6,7 +6,7 @@ defmodule Drowzee.Controller.SleepScheduleController do use Bonny.ControllerV2 import Drowzee.Axn require Logger - alias Drowzee.K8s.{SleepSchedule, Ingress, Deployment} + alias Drowzee.K8s.{SleepSchedule, Ingress, Deployment, StatefulSet, CronJob} step Bonny.Pluggable.SkipObservedGenerations step :handle_event @@ -129,11 +129,15 @@ defmodule Drowzee.Controller.SleepScheduleController do defp scale_down_deployments(axn) do SleepSchedule.scale_down_deployments(axn.resource) + SleepSchedule.scale_down_stateful_sets(axn.resource) + SleepSchedule.suspend_cron_jobs(axn.resource) axn end defp scale_up_deployments(axn) do SleepSchedule.scale_up_deployments(axn.resource) + SleepSchedule.scale_up_stateful_sets(axn.resource) + SleepSchedule.resume_cron_jobs(axn.resource) axn end @@ -194,23 +198,66 @@ defmodule Drowzee.Controller.SleepScheduleController do end defp deployment_status_ready?(status) do - status["replicas"] != nil && status["readyReplicas"] != nil && status["replicas"] == status["readyReplicas"] + # For deployments and stateful sets + if Map.has_key?(status, "replicas") do + status["replicas"] != nil && status["readyReplicas"] != nil && status["replicas"] == status["readyReplicas"] + # For cron jobs + else + Map.get(status, "suspended", true) == false + end end defp deployment_status_asleep?(status) do - Map.get(status, "replicas", 0) == 0 && Map.get(status, "readyReplicas", 0) == 0 + # For deployments and stateful sets + if Map.has_key?(status, "replicas") do + Map.get(status, "replicas", 0) == 0 && Map.get(status, "readyReplicas", 0) == 0 + # For cron jobs + else + Map.get(status, "suspended", false) == true + end end defp check_deployment_status(axn, check_fn) do - case SleepSchedule.get_deployments(axn.resource) do - {:ok, deployments} -> - result = Enum.all?(deployments, fn deployment -> - Logger.debug("Deployment #{Deployment.name(deployment)} replicas: #{Deployment.replicas(deployment)}, readyReplicas: #{Deployment.ready_replicas(deployment)}") - check_fn.(deployment["status"]) - end) - {:ok, result} - {:error, error} -> - {:error, error} + with {:ok, deployments} <- SleepSchedule.get_deployments(axn.resource), + {:ok, stateful_sets} <- SleepSchedule.get_stateful_sets(axn.resource), + {:ok, cron_jobs} <- SleepSchedule.get_cron_jobs(axn.resource) do + + deployments_result = Enum.all?(deployments, fn deployment -> + Logger.debug("Deployment #{Deployment.name(deployment)} replicas: #{Deployment.replicas(deployment)}, readyReplicas: #{Deployment.ready_replicas(deployment)}") + check_fn.(deployment["status"]) + end) + + stateful_sets_result = Enum.all?(stateful_sets, fn stateful_set -> + Logger.debug("StatefulSet #{StatefulSet.name(stateful_set)} replicas: #{StatefulSet.replicas(stateful_set)}, readyReplicas: #{StatefulSet.ready_replicas(stateful_set)}") + check_fn.(stateful_set["status"]) + end) + + cron_jobs_result = Enum.all?(cron_jobs, fn cron_job -> + suspended = CronJob.suspend(cron_job) + Logger.debug("CronJob #{CronJob.name(cron_job)} suspended: #{suspended}") + # For sleeping, we want all jobs to be suspended (true) + # For waking, we want all jobs to not be suspended (false) + # check_fn will determine which state we're checking for + check_fn.(%{"suspended" => suspended}) + end) + + # Determine which resources we need to check based on what's present + has_deployments = !Enum.empty?(deployments) + has_stateful_sets = !Enum.empty?(stateful_sets) + has_cron_jobs = !Enum.empty?(cron_jobs) + + cond do + !has_deployments && !has_stateful_sets && !has_cron_jobs -> {:ok, true} # No resources to check + has_deployments && !has_stateful_sets && !has_cron_jobs -> {:ok, deployments_result} + !has_deployments && has_stateful_sets && !has_cron_jobs -> {:ok, stateful_sets_result} + !has_deployments && !has_stateful_sets && has_cron_jobs -> {:ok, cron_jobs_result} + has_deployments && has_stateful_sets && !has_cron_jobs -> {:ok, deployments_result && stateful_sets_result} + has_deployments && !has_stateful_sets && has_cron_jobs -> {:ok, deployments_result && cron_jobs_result} + !has_deployments && has_stateful_sets && has_cron_jobs -> {:ok, stateful_sets_result && cron_jobs_result} + true -> {:ok, deployments_result && stateful_sets_result && cron_jobs_result} # Check all three + end + else + {:error, error} -> {:error, error} end end @@ -220,7 +267,7 @@ defmodule Drowzee.Controller.SleepScheduleController do sleep_reason = if manual_override, do: "ManualSleep", else: "ScheduledSleep" axn |> set_condition("Transitioning", false, "NoTransition", "No transition in progress") - |> set_condition("Sleeping", true, sleep_reason, "Deployments have been scaled down and ingress redirected.") + |> set_condition("Sleeping", true, sleep_reason, "Deployments, StatefulSets, and CronJobs have been scaled down/suspended and ingress redirected.") |> set_condition("Error", false, "None", "No error") end @@ -230,7 +277,7 @@ defmodule Drowzee.Controller.SleepScheduleController do wake_reason = if manual_override, do: "ManualWakeUp", else: "ScheduledWakeUp" axn |> set_condition("Transitioning", false, "NoTransition", "No transition in progress") - |> set_condition("Sleeping", false, wake_reason, "Deployments have been scaled up and ingress restored.") + |> set_condition("Sleeping", false, wake_reason, "Deployments and StatefulSets have been scaled up, CronJobs have been resumed, and ingress restored.") |> set_condition("Error", false, "None", "No error") end diff --git a/lib/drowzee/k8s.ex b/lib/drowzee/k8s.ex index 1b6ee28..5668b0b 100644 --- a/lib/drowzee/k8s.ex +++ b/lib/drowzee/k8s.ex @@ -95,4 +95,18 @@ defmodule Drowzee.K8s do |> K8s.Client.put_conn(conn()) |> K8s.Client.run() end + + def get_stateful_set(name, namespace) do + Logger.debug("Fetching stateful set", stateful_set_name: name) + K8s.Client.get("apps/v1", :statefulset, name: name, namespace: namespace) + |> K8s.Client.put_conn(conn()) + |> K8s.Client.run() + end + + def get_cron_job(name, namespace) do + Logger.debug("Fetching cron job", cron_job_name: name) + K8s.Client.get("batch/v1", :cronjob, name: name, namespace: namespace) + |> K8s.Client.put_conn(conn()) + |> K8s.Client.run() + end end diff --git a/lib/drowzee/k8s/cron_job.ex b/lib/drowzee/k8s/cron_job.ex new file mode 100644 index 0000000..28658d1 --- /dev/null +++ b/lib/drowzee/k8s/cron_job.ex @@ -0,0 +1,26 @@ +defmodule Drowzee.K8s.CronJob do + require Logger + + def name(cron_job) do + cron_job["metadata"]["name"] + end + + def namespace(cron_job) do + cron_job["metadata"]["namespace"] + end + + def suspend(cron_job) do + cron_job["spec"]["suspend"] || false + end + + def suspend_cron_job(%{"kind" => "CronJob"} = cron_job, suspend) do + Logger.info("Setting cron job suspend", cron_job: name(cron_job), suspend: suspend) + cron_job = put_in(cron_job["spec"]["suspend"], suspend) + case K8s.Client.run(Drowzee.K8s.conn(), K8s.Client.update(cron_job)) do + {:ok, cron_job} -> {:ok, cron_job} + {:error, reason} -> + Logger.error("Failed to suspend cron job: #{inspect(reason)}", cron_job: name(cron_job), suspend: suspend) + {:error, reason} + end + end +end diff --git a/lib/drowzee/k8s/sleep_schedule.ex b/lib/drowzee/k8s/sleep_schedule.ex index 075c686..0cd0da4 100644 --- a/lib/drowzee/k8s/sleep_schedule.ex +++ b/lib/drowzee/k8s/sleep_schedule.ex @@ -3,7 +3,7 @@ defmodule Drowzee.K8s.SleepSchedule do require Logger - alias Drowzee.K8s.{Deployment, Ingress, Condition} + alias Drowzee.K8s.{Deployment, StatefulSet, CronJob, Ingress, Condition} def name(sleep_schedule) do sleep_schedule["metadata"]["name"] @@ -18,6 +18,16 @@ defmodule Drowzee.K8s.SleepSchedule do |> Enum.map(& &1["name"]) end + def stateful_set_names(sleep_schedule) do + (sleep_schedule["spec"]["statefulSets"] || []) + |> Enum.map(& &1["name"]) + end + + def cron_job_names(sleep_schedule) do + (sleep_schedule["spec"]["cronJobs"] || []) + |> Enum.map(& &1["name"]) + end + def ingress_name(sleep_schedule) do sleep_schedule["spec"]["ingressName"] end @@ -66,6 +76,32 @@ defmodule Drowzee.K8s.SleepSchedule do end end + @retry with: exponential_backoff(1000) |> Stream.take(2) + def get_stateful_sets(sleep_schedule) do + namespace = namespace(sleep_schedule) + results = (stateful_set_names(sleep_schedule) || []) + |> Stream.map(&Drowzee.K8s.get_stateful_set(&1, namespace)) + |> Enum.to_list() + + case Enum.all?(results, fn {:ok, _} -> true; _ -> false end) do + true -> {:ok, Enum.map(results, fn {:ok, stateful_set} -> stateful_set end)} + false -> {:error, Enum.filter(results, fn {:error, _} -> true; _ -> false end) |> Enum.map(fn {:error, error} -> error end)} + end + end + + @retry with: exponential_backoff(1000) |> Stream.take(2) + def get_cron_jobs(sleep_schedule) do + namespace = namespace(sleep_schedule) + results = (cron_job_names(sleep_schedule) || []) + |> Stream.map(&Drowzee.K8s.get_cron_job(&1, namespace)) + |> Enum.to_list() + + case Enum.all?(results, fn {:ok, _} -> true; _ -> false end) do + true -> {:ok, Enum.map(results, fn {:ok, cron_job} -> cron_job end)} + false -> {:error, Enum.filter(results, fn {:error, _} -> true; _ -> false end) |> Enum.map(fn {:error, error} -> error end)} + end + end + def scale_down_deployments(sleep_schedule) do Logger.debug("Scaling down deployments...") case get_deployments(sleep_schedule) do @@ -77,6 +113,28 @@ defmodule Drowzee.K8s.SleepSchedule do end end + def scale_down_stateful_sets(sleep_schedule) do + Logger.debug("Scaling down stateful sets...") + case get_stateful_sets(sleep_schedule) do + {:ok, stateful_sets} -> + results = Enum.map(stateful_sets, &StatefulSet.scale_stateful_set(&1, 0)) + {:ok, results} + {:error, error} -> + {:error, error} + end + end + + def suspend_cron_jobs(sleep_schedule) do + Logger.debug("Suspending cron jobs...") + case get_cron_jobs(sleep_schedule) do + {:ok, cron_jobs} -> + results = Enum.map(cron_jobs, &CronJob.suspend_cron_job(&1, true)) + {:ok, results} + {:error, error} -> + {:error, error} + end + end + def scale_up_deployments(sleep_schedule) do Logger.debug("Scaling up deployments...") case get_deployments(sleep_schedule) do @@ -88,6 +146,28 @@ defmodule Drowzee.K8s.SleepSchedule do end end + def scale_up_stateful_sets(sleep_schedule) do + Logger.debug("Scaling up stateful sets...") + case get_stateful_sets(sleep_schedule) do + {:ok, stateful_sets} -> + results = Enum.map(stateful_sets, &StatefulSet.scale_stateful_set(&1, 1)) + {:ok, results} + {:error, error} -> + {:error, error} + end + end + + def resume_cron_jobs(sleep_schedule) do + Logger.debug("Resuming cron jobs...") + case get_cron_jobs(sleep_schedule) do + {:ok, cron_jobs} -> + results = Enum.map(cron_jobs, &CronJob.suspend_cron_job(&1, false)) + {:ok, results} + {:error, error} -> + {:error, error} + end + end + def put_ingress_to_sleep(sleep_schedule) do with {:ok, ingress} <- get_ingress(sleep_schedule), {:ok, drowzee_ingress} <- Drowzee.K8s.get_drowzee_ingress(), diff --git a/lib/drowzee/k8s/stateful_set.ex b/lib/drowzee/k8s/stateful_set.ex new file mode 100644 index 0000000..397a84c --- /dev/null +++ b/lib/drowzee/k8s/stateful_set.ex @@ -0,0 +1,30 @@ +defmodule Drowzee.K8s.StatefulSet do + require Logger + + def name(stateful_set) do + stateful_set["metadata"]["name"] + end + + def namespace(stateful_set) do + stateful_set["metadata"]["namespace"] + end + + def replicas(stateful_set) do + stateful_set["spec"]["replicas"] || 0 + end + + def ready_replicas(stateful_set) do + stateful_set["status"]["readyReplicas"] || 0 + end + + def scale_stateful_set(%{"kind" => "StatefulSet"} = stateful_set, replicas) do + Logger.info("Scaling stateful set", stateful_set: name(stateful_set), replicas: replicas) + stateful_set = put_in(stateful_set["spec"]["replicas"], replicas) + case K8s.Client.run(Drowzee.K8s.conn(), K8s.Client.update(stateful_set)) do + {:ok, stateful_set} -> {:ok, stateful_set} + {:error, reason} -> + Logger.error("Failed to scale stateful set: #{inspect(reason)}", stateful_set: name(stateful_set), replicas: replicas) + {:error, reason} + end + end +end From 8a07bedc8c6466492fd7c1bc10400fbf03f2efa8 Mon Sep 17 00:00:00 2001 From: Ilya Semerhanov Date: Tue, 6 May 2025 23:46:00 +0200 Subject: [PATCH 02/62] make waketime optional --- chart/crds/sleepschedule.yaml | 1 - .../controllers/sleep_schedule_controller.ex | 24 +++++++++++++++++-- lib/drowzee/sleep_checker.ex | 11 +++++++++ sleep_schedule.yaml | 17 +++++++++++++ 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/chart/crds/sleepschedule.yaml b/chart/crds/sleepschedule.yaml index 3703b53..545cc3d 100644 --- a/chart/crds/sleepschedule.yaml +++ b/chart/crds/sleepschedule.yaml @@ -52,7 +52,6 @@ spec: required: - sleepTime - timezone - - wakeTime properties: deployments: type: array diff --git a/lib/drowzee/controllers/sleep_schedule_controller.ex b/lib/drowzee/controllers/sleep_schedule_controller.ex index 86b3217..ca97f0f 100644 --- a/lib/drowzee/controllers/sleep_schedule_controller.ex +++ b/lib/drowzee/controllers/sleep_schedule_controller.ex @@ -54,6 +54,12 @@ defmodule Drowzee.Controller.SleepScheduleController do sleep_time = resource["spec"]["sleepTime"] wake_time = resource["spec"]["wakeTime"] timezone = resource["spec"]["timezone"] + + # Log if wake_time is not defined (resources will remain down indefinitely) + if is_nil(wake_time) or wake_time == "" do + Logger.info("Wake time is not defined for #{resource["metadata"]["name"]} in namespace #{resource["metadata"]["namespace"]}. Resources will remain scaled down/suspended indefinitely.") + end + case Drowzee.SleepChecker.naptime?(sleep_time, wake_time, timezone) do {:ok, naptime} -> %{axn | assigns: Map.put(axn.assigns, :naptime, naptime)} @@ -264,10 +270,24 @@ defmodule Drowzee.Controller.SleepScheduleController do defp complete_sleep_transition(axn, opts) do Logger.info("Sleep transition complete") manual_override = Keyword.get(opts, :manual_override, false) - sleep_reason = if manual_override, do: "ManualSleep", else: "ScheduledSleep" + wake_time = axn.resource["spec"]["wakeTime"] + permanent_sleep = is_nil(wake_time) or wake_time == "" + + sleep_reason = cond do + manual_override -> "ManualSleep" + permanent_sleep -> "PermanentSleep" + true -> "ScheduledSleep" + end + + sleep_message = if permanent_sleep do + "Deployments, StatefulSets, and CronJobs have been scaled down/suspended indefinitely and ingress redirected." + else + "Deployments, StatefulSets, and CronJobs have been scaled down/suspended and ingress redirected." + end + axn |> set_condition("Transitioning", false, "NoTransition", "No transition in progress") - |> set_condition("Sleeping", true, sleep_reason, "Deployments, StatefulSets, and CronJobs have been scaled down/suspended and ingress redirected.") + |> set_condition("Sleeping", true, sleep_reason, sleep_message) |> set_condition("Error", false, "None", "No error") end diff --git a/lib/drowzee/sleep_checker.ex b/lib/drowzee/sleep_checker.ex index 7226c4f..9902b7d 100644 --- a/lib/drowzee/sleep_checker.ex +++ b/lib/drowzee/sleep_checker.ex @@ -1,6 +1,17 @@ defmodule Drowzee.SleepChecker do require Logger + def naptime?(sleep_time, nil, timezone) do + # If wake_time is nil, always consider it naptime (always sleeping) + Logger.debug("Wake time is not defined, resources will remain scaled down/suspended indefinitely") + {:ok, true} + end + + def naptime?(sleep_time, "", timezone) do + # Empty string wake_time is treated the same as nil + naptime?(sleep_time, nil, timezone) + end + def naptime?(sleep_time, wake_time, timezone) do now = DateTime.now!(timezone) today_date = DateTime.to_date(now) diff --git a/sleep_schedule.yaml b/sleep_schedule.yaml index a226e04..18cd325 100644 --- a/sleep_schedule.yaml +++ b/sleep_schedule.yaml @@ -37,3 +37,20 @@ spec: - name: service2 - name: service2-worker ingressName: service2 +--- +apiVersion: drowzee.challengr.io/v1beta1 +kind: SleepSchedule +metadata: + name: permanent-down + namespace: services +spec: + sleepTime: '08:00pm' + # No wakeTime defined - resources will remain scaled down indefinitely + timezone: 'Australia/Sydney' + deployments: + - name: non-prod-service + statefulSets: + - name: non-prod-database + cronJobs: + - name: non-prod-backup + ingressName: non-prod-service From db3d7b200fbe53c428fcc9d79857d1d09a84f1dc Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 11:30:46 +0200 Subject: [PATCH 03/62] feat: Update Github workflow --- .github/CODEOWNERS | 4 ++ .github/dependabot.yml | 10 +++++ .github/pull_request_template.md | 36 +++++++++++++++ .github/workflows/docker-build-push.yml | 44 ------------------ .github/workflows/helm-publish.yml | 59 ------------------------- .github/workflows/pipeline-build.yml | 23 ++++++++++ 6 files changed, 73 insertions(+), 103 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/dependabot.yml create mode 100644 .github/pull_request_template.md delete mode 100644 .github/workflows/docker-build-push.yml delete mode 100644 .github/workflows/helm-publish.yml create mode 100644 .github/workflows/pipeline-build.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..4189f5a --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +# CODEOWNERS file allows us to set owners for specific files and directories +# https://docs.github.com/en/enterprise-cloud@latest/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners + +* @chirpwireless/infra diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..fde5816 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: terraform + directory: "/" + schedule: + interval: weekly + groups: + all: + patterns: + - "*" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..b39b965 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,36 @@ +## Motivation + + + +... + +## Proposed changes + + + +... + +## Unrelated changes (optional) + + + +_None_ + +## Testing / Validation + + + +... + +## Notes (optional) + + + +_None_ + +## Dependencies (optional) + + + +_None_ \ No newline at end of file diff --git a/.github/workflows/docker-build-push.yml b/.github/workflows/docker-build-push.yml deleted file mode 100644 index 84a716c..0000000 --- a/.github/workflows/docker-build-push.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Docker Build and Push - -on: - push: - tags: [ "v*" ] - -jobs: - build-and-push: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Extract version from tag - if: startsWith(github.ref, 'refs/tags/') - id: extract_version - run: | - VERSION=${GITHUB_REF#refs/tags/} - VERSION=${VERSION#v} - echo "version=$VERSION" >> $GITHUB_OUTPUT - - - name: Build and push - uses: docker/build-push-action@v4 - with: - context: . - file: ./Dockerfile - platforms: linux/amd64,linux/arm64 - push: true - tags: | - colharris/drowzee:latest - colharris/drowzee:${{ github.sha }} - ${{ steps.extract_version.outputs.version && format('colharris/drowzee:{0}', steps.extract_version.outputs.version) || '' }} diff --git a/.github/workflows/helm-publish.yml b/.github/workflows/helm-publish.yml deleted file mode 100644 index f83370a..0000000 --- a/.github/workflows/helm-publish.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Helm Chart Publisher - -on: - push: - tags: [ "v*" ] - -jobs: - publish: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Extract version from tag - if: startsWith(github.ref, 'refs/tags/') - id: extract_version - run: | - VERSION=${GITHUB_REF#refs/tags/} - VERSION=${VERSION#v} - echo "version=$VERSION" >> $GITHUB_OUTPUT - - - name: Update Chart Versions - run: | - sed -i "s/^version: .*/version: ${{ steps.extract_version.outputs.version }}/" chart/Chart.yaml - sed -i "s/^appVersion: .*/appVersion: \"${{ steps.extract_version.outputs.version }}\"/" chart/Chart.yaml - - - name: Retrieve existing charts - run: | - git clone --branch gh-pages https://github.com/col/drowzee.git existing-charts - mkdir -p packaged-charts - cp -n existing-charts/*.tgz packaged-charts/ || true - - - name: Set up Helm - uses: azure/setup-helm@v3 - - - name: Package Chart - run: helm package ./chart --destination ./packaged-charts - - - name: Generate Repository Index - run: | - helm repo index ./packaged-charts --url https://col.github.io/drowzee - - - name: Configure Git - run: | - git config user.name "GitHub Actions" - git config user.email "actions@github.com" - - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./packaged-charts - keep_files: true - force_orphan: true - destination_dir: . diff --git a/.github/workflows/pipeline-build.yml b/.github/workflows/pipeline-build.yml new file mode 100644 index 0000000..3623c9d --- /dev/null +++ b/.github/workflows/pipeline-build.yml @@ -0,0 +1,23 @@ +name: Build + +on: + workflow_dispatch: + push: + branches: + - feature/sts_jobs + +concurrency: + # One build per PR, do not cancel builds on main + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + validate: + uses: chirpwireless/reusable-github-actions/.github/workflows/build-k8s-service.yml@main + permissions: + contents: write + packages: write + checks: write + secrets: inherit + with: + service_name: "drowzee" From a11508e6ed6b82b02efe1aef86be3624e9110d19 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 11:33:41 +0200 Subject: [PATCH 04/62] feat: Small fixes --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 864db07..32d7471 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ ENV MIX_ENV=prod \ # && apt-get clean && rm -f /var/lib/apt/lists/*_* RUN mix local.hex --force && \ - mix local.rebar --force + mix local.rebar --force WORKDIR /app From eb6a7eef66d39a85389e7a9b783825a8f7bd1783 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 11:45:22 +0200 Subject: [PATCH 05/62] feat: Update workflow --- .github/workflows/pipeline-build.yml | 107 +++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pipeline-build.yml b/.github/workflows/pipeline-build.yml index 3623c9d..2c44ea1 100644 --- a/.github/workflows/pipeline-build.yml +++ b/.github/workflows/pipeline-build.yml @@ -11,13 +11,106 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true +env: + HOME: ${{github.workspace}} + jobs: - validate: - uses: chirpwireless/reusable-github-actions/.github/workflows/build-k8s-service.yml@main + generate-version: + runs-on: + group: scw-k8s-dev + outputs: + service-version: ${{ steps.version.outputs.new_version }} + steps: + - uses: actions/checkout@v4 + - id: version + uses: chirpwireless/reusable-github-actions/.github/actions/calendar-version@main + - name: "Create a summary" + run: | + echo "Building a new revision ${{steps.version.outputs.new_version}}" > $GITHUB_STEP_SUMMARY + + build-docker-image: + runs-on: + group: scw-k8s-dev + needs: [generate-version] + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ghcr.io/${{ github.repository_owner }}/drowzee:${{ needs.generate-version.outputs.service-version }} + + build-helm-chart: + runs-on: + group: scw-k8s-dev + needs: [generate-version] + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Update Chart Versions + run: | + sed -i "s/^version: .*/version: ${{ needs.generate-version.outputs.service-version }}/" chart/Chart.yaml + sed -i "s/^appVersion: .*/appVersion: \"${{ needs.generate-version.outputs.service-version }}\"/" chart/Chart.yaml + - name: Retrieve existing charts + run: | + git clone --branch gh-pages https://github.com/${{ github.repository_owner }}/drowzee.git existing-charts || true + mkdir -p packaged-charts + cp -n existing-charts/*.tgz packaged-charts/ || true + - name: Set up Helm + uses: azure/setup-helm@v4 + - name: Package Chart + run: helm package ./chart --destination ./packaged-charts + - name: Generate Repository Index + run: | + helm repo index ./packaged-charts --url https://${{ github.repository_owner }}.github.io/drowzee + # Popular action to deploy to GitHub Pages: + # Docs: https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-docusaurus + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + # Build output to publish to the `gh-pages` branch: + publish_dir: ./packaged-charts + keep_files: true + force_orphan: true + destination_dir: . + # The following lines assign commit authorship to the official + # GH-Actions bot for deploys to `gh-pages` branch: + # https://github.com/actions/checkout/issues/13#issuecomment-724415212 + # The GH actions bot is used by default if you didn't specify the two fields. + # You can swap them out with your own user credentials. + user_name: github-actions[bot] + user_email: 41898282+github-actions[bot]@users.noreply.github.com + + publish-version: + runs-on: + group: scw-k8s-dev + needs: [generate-version, build-docker-image, build-helm-chart] permissions: - contents: write - packages: write checks: write - secrets: inherit - with: - service_name: "drowzee" + steps: + - name: Publish Version + uses: chirpwireless/reusable-github-actions/.github/actions/version-publish@main + with: + commit-sha: ${{ github.event.pull_request.head.sha || github.sha }} + build-version: ${{ needs.generate-version.outputs.service-version }} + + publish-release: + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' + runs-on: + group: scw-k8s-dev + needs: [generate-version, build-docker-image, build-helm-chart] + steps: + - uses: chirpwireless/reusable-github-actions/.github/actions/publish-release@main + with: + version: ${{ needs.generate-version.outputs.service-version }} From 2d5203445ce0c91de26708a54760719140ce2bf1 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 11:46:37 +0200 Subject: [PATCH 06/62] feat: Update workflow --- .github/workflows/pipeline-build.yml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pipeline-build.yml b/.github/workflows/pipeline-build.yml index 2c44ea1..f726771 100644 --- a/.github/workflows/pipeline-build.yml +++ b/.github/workflows/pipeline-build.yml @@ -16,8 +16,7 @@ env: jobs: generate-version: - runs-on: - group: scw-k8s-dev + runs-on: ubuntu-latest outputs: service-version: ${{ steps.version.outputs.new_version }} steps: @@ -29,8 +28,7 @@ jobs: echo "Building a new revision ${{steps.version.outputs.new_version}}" > $GITHUB_STEP_SUMMARY build-docker-image: - runs-on: - group: scw-k8s-dev + runs-on: ubuntu-latest needs: [generate-version] steps: - name: Checkout code @@ -51,8 +49,7 @@ jobs: tags: ghcr.io/${{ github.repository_owner }}/drowzee:${{ needs.generate-version.outputs.service-version }} build-helm-chart: - runs-on: - group: scw-k8s-dev + runs-on: ubuntu-latest needs: [generate-version] steps: - name: Checkout code @@ -93,8 +90,7 @@ jobs: user_email: 41898282+github-actions[bot]@users.noreply.github.com publish-version: - runs-on: - group: scw-k8s-dev + runs-on: ubuntu-latest needs: [generate-version, build-docker-image, build-helm-chart] permissions: checks: write @@ -107,8 +103,7 @@ jobs: publish-release: if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' - runs-on: - group: scw-k8s-dev + runs-on: ubuntu-latest needs: [generate-version, build-docker-image, build-helm-chart] steps: - uses: chirpwireless/reusable-github-actions/.github/actions/publish-release@main From db9b1e3273ad2755c97bdeba10382ce646284a97 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 11:52:59 +0200 Subject: [PATCH 07/62] fix: Update permissions --- .github/workflows/pipeline-build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/pipeline-build.yml b/.github/workflows/pipeline-build.yml index f726771..c8b7ff8 100644 --- a/.github/workflows/pipeline-build.yml +++ b/.github/workflows/pipeline-build.yml @@ -30,6 +30,8 @@ jobs: build-docker-image: runs-on: ubuntu-latest needs: [generate-version] + permissions: + contents: write steps: - name: Checkout code uses: actions/checkout@v4 @@ -51,6 +53,8 @@ jobs: build-helm-chart: runs-on: ubuntu-latest needs: [generate-version] + permissions: + contents: write steps: - name: Checkout code uses: actions/checkout@v4 From e2a19816dd084dbf3f8a4e41b3bf08d743d0a163 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 11:56:25 +0200 Subject: [PATCH 08/62] fix: Update permissions --- .github/workflows/pipeline-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pipeline-build.yml b/.github/workflows/pipeline-build.yml index c8b7ff8..6c051f9 100644 --- a/.github/workflows/pipeline-build.yml +++ b/.github/workflows/pipeline-build.yml @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest needs: [generate-version] permissions: - contents: write + packages: write steps: - name: Checkout code uses: actions/checkout@v4 From f5195efb7e92f124a4e7f5ae253f025272e4e263 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 12:03:50 +0200 Subject: [PATCH 09/62] feat: Update helm chart --- chart/crds/sleepschedule.yaml | 10 +++++----- chart/values.yaml | 18 +++++++++++------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/chart/crds/sleepschedule.yaml b/chart/crds/sleepschedule.yaml index 545cc3d..c75471e 100644 --- a/chart/crds/sleepschedule.yaml +++ b/chart/crds/sleepschedule.yaml @@ -33,8 +33,8 @@ spec: status: type: string enum: - - 'True' - - 'False' + - "True" + - "False" type: type: string reason: @@ -88,13 +88,13 @@ spec: description: The ingress that will be slept/woken. sleepTime: type: string - description: 'The time that the deployment will start sleeping(format: HH:MMam/pm)' + description: "The time that the deployment will start sleeping(format: HH:MMam/pm)" timezone: type: string description: The timezone that the input times are based in wakeTime: type: string - description: 'The time that the deployment will wake up (format: HH:MMam/pm)' + description: "The time that the deployment will wake up (format: HH:MMam/pm)" storage: true additionalPrinterColumns: - name: SleepTime @@ -140,4 +140,4 @@ metadata: labels: k8s-app: drowzee kind: CustomResourceDefinition -apiVersion: apiextensions.k8s.io/v1 \ No newline at end of file +apiVersion: apiextensions.k8s.io/v1 diff --git a/chart/values.yaml b/chart/values.yaml index 55ed0e8..44defa0 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -5,7 +5,7 @@ # Can either be `single_namespace` to manage the Drowzee release namespace or `cluster` to manage multiple namespaces mode: "single_namespace" -app: +app: # The host name for the application host: "drowzee.local" server: true @@ -18,7 +18,7 @@ app: config: [] secrets: - # Required - A random string used for signing and encrypting session data and cookies. + # Required - A random string used for signing and encrypting session data and cookies. # Can be generated with `openssl rand -hex 64` secretKeyBase: "" @@ -27,7 +27,7 @@ replicaCount: 1 # This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/ image: - repository: "colharris/drowzee" + repository: "ghcr.io/chirpwireless/drowzee" # This sets the pull policy for images. pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. @@ -58,10 +58,12 @@ podAnnotations: {} # For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ podLabels: {} -podSecurityContext: {} +podSecurityContext: + {} # fsGroup: 2000 -securityContext: {} +securityContext: + {} # capabilities: # drop: # - ALL @@ -80,7 +82,8 @@ service: ingress: enabled: false className: "" - annotations: {} + annotations: + {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" hosts: @@ -93,7 +96,8 @@ ingress: # hosts: # - chart-example.local -resources: {} +resources: + {} # We usually recommend not to specify default resources and to leave this as a conscious # choice for the user. This also increases chances charts run on environments with little # resources, such as Minikube. If you do want to specify resources, uncomment the following From c044a088532fc972bd1885897d4ba91a655eb204 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 12:38:04 +0200 Subject: [PATCH 10/62] fix: Update role --- chart/templates/role.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chart/templates/role.yaml b/chart/templates/role.yaml index dca3ccd..9de125e 100644 --- a/chart/templates/role.yaml +++ b/chart/templates/role.yaml @@ -38,8 +38,11 @@ rules: - '*' - resources: - deployments + - statefulsets + - cronjobs apiGroups: - "apps" + - "batch" verbs: - '*' - resources: From 0cccaecd8d939beb7e3e3f770fe3a11543fa0065 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 13:09:35 +0200 Subject: [PATCH 11/62] feat: Update web ui --- chart/crds/sleepschedule.yaml | 2 +- .../controllers/sleep_schedule_controller.ex | 30 +++++++++---------- lib/drowzee/k8s/sleep_schedule.ex | 22 +++++++------- .../live/home_live/index.html.heex | 22 ++++++++++++++ 4 files changed, 49 insertions(+), 27 deletions(-) diff --git a/chart/crds/sleepschedule.yaml b/chart/crds/sleepschedule.yaml index c75471e..8a8888e 100644 --- a/chart/crds/sleepschedule.yaml +++ b/chart/crds/sleepschedule.yaml @@ -65,7 +65,7 @@ spec: type: string statefulSets: type: array - description: The stateful sets that will be slept/woken. + description: The statefulsets that will be slept/woken. items: type: object required: diff --git a/lib/drowzee/controllers/sleep_schedule_controller.ex b/lib/drowzee/controllers/sleep_schedule_controller.ex index ca97f0f..247c469 100644 --- a/lib/drowzee/controllers/sleep_schedule_controller.ex +++ b/lib/drowzee/controllers/sleep_schedule_controller.ex @@ -135,14 +135,14 @@ defmodule Drowzee.Controller.SleepScheduleController do defp scale_down_deployments(axn) do SleepSchedule.scale_down_deployments(axn.resource) - SleepSchedule.scale_down_stateful_sets(axn.resource) + SleepSchedule.scale_down_statefulsets(axn.resource) SleepSchedule.suspend_cron_jobs(axn.resource) axn end defp scale_up_deployments(axn) do SleepSchedule.scale_up_deployments(axn.resource) - SleepSchedule.scale_up_stateful_sets(axn.resource) + SleepSchedule.scale_up_statefulsets(axn.resource) SleepSchedule.resume_cron_jobs(axn.resource) axn end @@ -204,7 +204,7 @@ defmodule Drowzee.Controller.SleepScheduleController do end defp deployment_status_ready?(status) do - # For deployments and stateful sets + # For deployments and statefulsets if Map.has_key?(status, "replicas") do status["replicas"] != nil && status["readyReplicas"] != nil && status["replicas"] == status["readyReplicas"] # For cron jobs @@ -214,7 +214,7 @@ defmodule Drowzee.Controller.SleepScheduleController do end defp deployment_status_asleep?(status) do - # For deployments and stateful sets + # For deployments and statefulsets if Map.has_key?(status, "replicas") do Map.get(status, "replicas", 0) == 0 && Map.get(status, "readyReplicas", 0) == 0 # For cron jobs @@ -225,7 +225,7 @@ defmodule Drowzee.Controller.SleepScheduleController do defp check_deployment_status(axn, check_fn) do with {:ok, deployments} <- SleepSchedule.get_deployments(axn.resource), - {:ok, stateful_sets} <- SleepSchedule.get_stateful_sets(axn.resource), + {:ok, statefulsets} <- SleepSchedule.get_statefulsets(axn.resource), {:ok, cron_jobs} <- SleepSchedule.get_cron_jobs(axn.resource) do deployments_result = Enum.all?(deployments, fn deployment -> @@ -233,7 +233,7 @@ defmodule Drowzee.Controller.SleepScheduleController do check_fn.(deployment["status"]) end) - stateful_sets_result = Enum.all?(stateful_sets, fn stateful_set -> + statefulsets_result = Enum.all?(statefulsets, fn stateful_set -> Logger.debug("StatefulSet #{StatefulSet.name(stateful_set)} replicas: #{StatefulSet.replicas(stateful_set)}, readyReplicas: #{StatefulSet.ready_replicas(stateful_set)}") check_fn.(stateful_set["status"]) end) @@ -249,18 +249,18 @@ defmodule Drowzee.Controller.SleepScheduleController do # Determine which resources we need to check based on what's present has_deployments = !Enum.empty?(deployments) - has_stateful_sets = !Enum.empty?(stateful_sets) + has_statefulsets = !Enum.empty?(statefulsets) has_cron_jobs = !Enum.empty?(cron_jobs) cond do - !has_deployments && !has_stateful_sets && !has_cron_jobs -> {:ok, true} # No resources to check - has_deployments && !has_stateful_sets && !has_cron_jobs -> {:ok, deployments_result} - !has_deployments && has_stateful_sets && !has_cron_jobs -> {:ok, stateful_sets_result} - !has_deployments && !has_stateful_sets && has_cron_jobs -> {:ok, cron_jobs_result} - has_deployments && has_stateful_sets && !has_cron_jobs -> {:ok, deployments_result && stateful_sets_result} - has_deployments && !has_stateful_sets && has_cron_jobs -> {:ok, deployments_result && cron_jobs_result} - !has_deployments && has_stateful_sets && has_cron_jobs -> {:ok, stateful_sets_result && cron_jobs_result} - true -> {:ok, deployments_result && stateful_sets_result && cron_jobs_result} # Check all three + !has_deployments && !has_statefulsets && !has_cron_jobs -> {:ok, true} # No resources to check + has_deployments && !has_statefulsets && !has_cron_jobs -> {:ok, deployments_result} + !has_deployments && has_statefulsets && !has_cron_jobs -> {:ok, statefulsets_result} + !has_deployments && !has_statefulsets && has_cron_jobs -> {:ok, cron_jobs_result} + has_deployments && has_statefulsets && !has_cron_jobs -> {:ok, deployments_result && statefulsets_result} + has_deployments && !has_statefulsets && has_cron_jobs -> {:ok, deployments_result && cron_jobs_result} + !has_deployments && has_statefulsets && has_cron_jobs -> {:ok, statefulsets_result && cron_jobs_result} + true -> {:ok, deployments_result && statefulsets_result && cron_jobs_result} # Check all three end else {:error, error} -> {:error, error} diff --git a/lib/drowzee/k8s/sleep_schedule.ex b/lib/drowzee/k8s/sleep_schedule.ex index 0cd0da4..08fd117 100644 --- a/lib/drowzee/k8s/sleep_schedule.ex +++ b/lib/drowzee/k8s/sleep_schedule.ex @@ -77,7 +77,7 @@ defmodule Drowzee.K8s.SleepSchedule do end @retry with: exponential_backoff(1000) |> Stream.take(2) - def get_stateful_sets(sleep_schedule) do + def get_statefulsets(sleep_schedule) do namespace = namespace(sleep_schedule) results = (stateful_set_names(sleep_schedule) || []) |> Stream.map(&Drowzee.K8s.get_stateful_set(&1, namespace)) @@ -113,11 +113,11 @@ defmodule Drowzee.K8s.SleepSchedule do end end - def scale_down_stateful_sets(sleep_schedule) do - Logger.debug("Scaling down stateful sets...") - case get_stateful_sets(sleep_schedule) do - {:ok, stateful_sets} -> - results = Enum.map(stateful_sets, &StatefulSet.scale_stateful_set(&1, 0)) + def scale_down_statefulsets(sleep_schedule) do + Logger.debug("Scaling down statefulsets...") + case get_statefulsets(sleep_schedule) do + {:ok, statefulsets} -> + results = Enum.map(statefulsets, &StatefulSet.scale_stateful_set(&1, 0)) {:ok, results} {:error, error} -> {:error, error} @@ -146,11 +146,11 @@ defmodule Drowzee.K8s.SleepSchedule do end end - def scale_up_stateful_sets(sleep_schedule) do - Logger.debug("Scaling up stateful sets...") - case get_stateful_sets(sleep_schedule) do - {:ok, stateful_sets} -> - results = Enum.map(stateful_sets, &StatefulSet.scale_stateful_set(&1, 1)) + def scale_up_statefulsets(sleep_schedule) do + Logger.debug("Scaling up statefulsets...") + case get_statefulsets(sleep_schedule) do + {:ok, statefulsets} -> + results = Enum.map(statefulsets, &StatefulSet.scale_stateful_set(&1, 1)) {:ok, results} {:error, error} -> {:error, error} diff --git a/lib/drowzee_web/live/home_live/index.html.heex b/lib/drowzee_web/live/home_live/index.html.heex index b637787..7304f98 100644 --- a/lib/drowzee_web/live/home_live/index.html.heex +++ b/lib/drowzee_web/live/home_live/index.html.heex @@ -161,6 +161,28 @@ +
+

📦 StatefulSets:

+
    + <%= for statefulset <- sleep_schedule["spec"]["statefulsets"] do %> +
  • + 🚀 <%= statefulset["name"] %> +
  • + <% end %> +
+
+ +
+

📦 CronJobs:

+
    + <%= for cron_job <- sleep_schedule["spec"]["cronjobs"] do %> +
  • + 🚀 <%= cron_job["name"] %> +
  • + <% end %> +
+
+ <%= if get_condition(sleep_schedule, "Error")["status"] == "True" do %>
From 26f2726a19c5065bf06619f0e732dd1e657bdc9f Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 13:16:16 +0200 Subject: [PATCH 12/62] fix: Update naming for statefulset --- chart/crds/sleepschedule.yaml | 4 +-- .../controllers/sleep_schedule_controller.ex | 6 ++-- lib/drowzee/k8s.ex | 4 +-- lib/drowzee/k8s/sleep_schedule.ex | 14 +++++----- lib/drowzee/k8s/stateful_set.ex | 28 +++++++++---------- sleep_schedule.yaml | 24 ++++++++-------- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/chart/crds/sleepschedule.yaml b/chart/crds/sleepschedule.yaml index 8a8888e..0fcda4b 100644 --- a/chart/crds/sleepschedule.yaml +++ b/chart/crds/sleepschedule.yaml @@ -63,7 +63,7 @@ spec: properties: name: type: string - statefulSets: + statefulsets: type: array description: The statefulsets that will be slept/woken. items: @@ -116,7 +116,7 @@ spec: - name: StatefulSets type: string description: StatefulSets - jsonPath: .spec.statefulSets[*].name + jsonPath: .spec.statefulsets[*].name - name: CronJobs type: string description: CronJobs diff --git a/lib/drowzee/controllers/sleep_schedule_controller.ex b/lib/drowzee/controllers/sleep_schedule_controller.ex index 247c469..c8868f0 100644 --- a/lib/drowzee/controllers/sleep_schedule_controller.ex +++ b/lib/drowzee/controllers/sleep_schedule_controller.ex @@ -233,9 +233,9 @@ defmodule Drowzee.Controller.SleepScheduleController do check_fn.(deployment["status"]) end) - statefulsets_result = Enum.all?(statefulsets, fn stateful_set -> - Logger.debug("StatefulSet #{StatefulSet.name(stateful_set)} replicas: #{StatefulSet.replicas(stateful_set)}, readyReplicas: #{StatefulSet.ready_replicas(stateful_set)}") - check_fn.(stateful_set["status"]) + statefulsets_result = Enum.all?(statefulsets, fn statefulset -> + Logger.debug("StatefulSet #{StatefulSet.name(statefulset)} replicas: #{StatefulSet.replicas(statefulset)}, readyReplicas: #{StatefulSet.ready_replicas(statefulset)}") + check_fn.(statefulset["status"]) end) cron_jobs_result = Enum.all?(cron_jobs, fn cron_job -> diff --git a/lib/drowzee/k8s.ex b/lib/drowzee/k8s.ex index 5668b0b..a83ca70 100644 --- a/lib/drowzee/k8s.ex +++ b/lib/drowzee/k8s.ex @@ -96,8 +96,8 @@ defmodule Drowzee.K8s do |> K8s.Client.run() end - def get_stateful_set(name, namespace) do - Logger.debug("Fetching stateful set", stateful_set_name: name) + def get_statefulset(name, namespace) do + Logger.debug("Fetching statefulset", statefulset_name: name) K8s.Client.get("apps/v1", :statefulset, name: name, namespace: namespace) |> K8s.Client.put_conn(conn()) |> K8s.Client.run() diff --git a/lib/drowzee/k8s/sleep_schedule.ex b/lib/drowzee/k8s/sleep_schedule.ex index 08fd117..41e9503 100644 --- a/lib/drowzee/k8s/sleep_schedule.ex +++ b/lib/drowzee/k8s/sleep_schedule.ex @@ -18,8 +18,8 @@ defmodule Drowzee.K8s.SleepSchedule do |> Enum.map(& &1["name"]) end - def stateful_set_names(sleep_schedule) do - (sleep_schedule["spec"]["statefulSets"] || []) + def statefulset_names(sleep_schedule) do + (sleep_schedule["spec"]["statefulsets"] || []) |> Enum.map(& &1["name"]) end @@ -79,12 +79,12 @@ defmodule Drowzee.K8s.SleepSchedule do @retry with: exponential_backoff(1000) |> Stream.take(2) def get_statefulsets(sleep_schedule) do namespace = namespace(sleep_schedule) - results = (stateful_set_names(sleep_schedule) || []) - |> Stream.map(&Drowzee.K8s.get_stateful_set(&1, namespace)) + results = (statefulset_names(sleep_schedule) || []) + |> Stream.map(&Drowzee.K8s.get_statefulset(&1, namespace)) |> Enum.to_list() case Enum.all?(results, fn {:ok, _} -> true; _ -> false end) do - true -> {:ok, Enum.map(results, fn {:ok, stateful_set} -> stateful_set end)} + true -> {:ok, Enum.map(results, fn {:ok, statefulset} -> statefulset end)} false -> {:error, Enum.filter(results, fn {:error, _} -> true; _ -> false end) |> Enum.map(fn {:error, error} -> error end)} end end @@ -117,7 +117,7 @@ defmodule Drowzee.K8s.SleepSchedule do Logger.debug("Scaling down statefulsets...") case get_statefulsets(sleep_schedule) do {:ok, statefulsets} -> - results = Enum.map(statefulsets, &StatefulSet.scale_stateful_set(&1, 0)) + results = Enum.map(statefulsets, &StatefulSet.scale_statefulset(&1, 0)) {:ok, results} {:error, error} -> {:error, error} @@ -150,7 +150,7 @@ defmodule Drowzee.K8s.SleepSchedule do Logger.debug("Scaling up statefulsets...") case get_statefulsets(sleep_schedule) do {:ok, statefulsets} -> - results = Enum.map(statefulsets, &StatefulSet.scale_stateful_set(&1, 1)) + results = Enum.map(statefulsets, &StatefulSet.scale_statefulset(&1, 1)) {:ok, results} {:error, error} -> {:error, error} diff --git a/lib/drowzee/k8s/stateful_set.ex b/lib/drowzee/k8s/stateful_set.ex index 397a84c..08598d6 100644 --- a/lib/drowzee/k8s/stateful_set.ex +++ b/lib/drowzee/k8s/stateful_set.ex @@ -1,29 +1,29 @@ defmodule Drowzee.K8s.StatefulSet do require Logger - def name(stateful_set) do - stateful_set["metadata"]["name"] + def name(statefulset) do + statefulset["metadata"]["name"] end - def namespace(stateful_set) do - stateful_set["metadata"]["namespace"] + def namespace(statefulset) do + statefulset["metadata"]["namespace"] end - def replicas(stateful_set) do - stateful_set["spec"]["replicas"] || 0 + def replicas(statefulset) do + statefulset["spec"]["replicas"] || 0 end - def ready_replicas(stateful_set) do - stateful_set["status"]["readyReplicas"] || 0 + def ready_replicas(statefulset) do + statefulset["status"]["readyReplicas"] || 0 end - def scale_stateful_set(%{"kind" => "StatefulSet"} = stateful_set, replicas) do - Logger.info("Scaling stateful set", stateful_set: name(stateful_set), replicas: replicas) - stateful_set = put_in(stateful_set["spec"]["replicas"], replicas) - case K8s.Client.run(Drowzee.K8s.conn(), K8s.Client.update(stateful_set)) do - {:ok, stateful_set} -> {:ok, stateful_set} + def scale_statefulset(%{"kind" => "StatefulSet"} = statefulset, replicas) do + Logger.info("Scaling statefulset", statefulset: name(statefulset), replicas: replicas) + statefulset = put_in(statefulset["spec"]["replicas"], replicas) + case K8s.Client.run(Drowzee.K8s.conn(), K8s.Client.update(statefulset)) do + {:ok, statefulset} -> {:ok, statefulset} {:error, reason} -> - Logger.error("Failed to scale stateful set: #{inspect(reason)}", stateful_set: name(stateful_set), replicas: replicas) + Logger.error("Failed to scale statefulset: #{inspect(reason)}", statefulset: name(statefulset), replicas: replicas) {:error, reason} end end diff --git a/sleep_schedule.yaml b/sleep_schedule.yaml index 18cd325..9ad150c 100644 --- a/sleep_schedule.yaml +++ b/sleep_schedule.yaml @@ -3,9 +3,9 @@ kind: SleepSchedule metadata: name: testing spec: - sleepTime: '10:00pm' - wakeTime: '09:00am' - timezone: 'Australia/Sydney' + sleepTime: "10:00pm" + wakeTime: "09:00am" + timezone: "Australia/Sydney" deployments: - name: dummy-service ingressName: dummy-service @@ -16,9 +16,9 @@ metadata: name: service1 namespace: services spec: - sleepTime: '10:00pm' - wakeTime: '09:00am' - timezone: 'Australia/Sydney' + sleepTime: "10:00pm" + wakeTime: "09:00am" + timezone: "Australia/Sydney" deployments: - name: service1 - name: service1-worker @@ -30,9 +30,9 @@ metadata: name: service2 namespace: services spec: - sleepTime: '10:00pm' - wakeTime: '09:00am' - timezone: 'Australia/Sydney' + sleepTime: "10:00pm" + wakeTime: "09:00am" + timezone: "Australia/Sydney" deployments: - name: service2 - name: service2-worker @@ -44,12 +44,12 @@ metadata: name: permanent-down namespace: services spec: - sleepTime: '08:00pm' + sleepTime: "08:00pm" # No wakeTime defined - resources will remain scaled down indefinitely - timezone: 'Australia/Sydney' + timezone: "Australia/Sydney" deployments: - name: non-prod-service - statefulSets: + statefulsets: - name: non-prod-database cronJobs: - name: non-prod-backup From 0784bc8830902014d57eca1959c0b5fbfa042664 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 13:19:15 +0200 Subject: [PATCH 13/62] feat: change naming for cronjobs --- chart/crds/sleepschedule.yaml | 6 +++--- lib/drowzee/controllers/sleep_schedule_controller.ex | 4 ++-- lib/drowzee/k8s.ex | 2 +- lib/drowzee/k8s/cron_job.ex | 4 ++-- lib/drowzee/k8s/sleep_schedule.ex | 6 +++--- sleep_schedule.yaml | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/chart/crds/sleepschedule.yaml b/chart/crds/sleepschedule.yaml index 0fcda4b..e04cde1 100644 --- a/chart/crds/sleepschedule.yaml +++ b/chart/crds/sleepschedule.yaml @@ -73,9 +73,9 @@ spec: properties: name: type: string - cronJobs: + cronjobs: type: array - description: The cron jobs that will be suspended/resumed. + description: The cronjobs that will be suspended/resumed. items: type: object required: @@ -120,7 +120,7 @@ spec: - name: CronJobs type: string description: CronJobs - jsonPath: .spec.cronJobs[*].name + jsonPath: .spec.cronjobs[*].name - name: Sleeping? type: string description: Current Status diff --git a/lib/drowzee/controllers/sleep_schedule_controller.ex b/lib/drowzee/controllers/sleep_schedule_controller.ex index c8868f0..e8edd52 100644 --- a/lib/drowzee/controllers/sleep_schedule_controller.ex +++ b/lib/drowzee/controllers/sleep_schedule_controller.ex @@ -207,7 +207,7 @@ defmodule Drowzee.Controller.SleepScheduleController do # For deployments and statefulsets if Map.has_key?(status, "replicas") do status["replicas"] != nil && status["readyReplicas"] != nil && status["replicas"] == status["readyReplicas"] - # For cron jobs + # For cronjobs else Map.get(status, "suspended", true) == false end @@ -217,7 +217,7 @@ defmodule Drowzee.Controller.SleepScheduleController do # For deployments and statefulsets if Map.has_key?(status, "replicas") do Map.get(status, "replicas", 0) == 0 && Map.get(status, "readyReplicas", 0) == 0 - # For cron jobs + # For cronjobs else Map.get(status, "suspended", false) == true end diff --git a/lib/drowzee/k8s.ex b/lib/drowzee/k8s.ex index a83ca70..81fe768 100644 --- a/lib/drowzee/k8s.ex +++ b/lib/drowzee/k8s.ex @@ -104,7 +104,7 @@ defmodule Drowzee.K8s do end def get_cron_job(name, namespace) do - Logger.debug("Fetching cron job", cron_job_name: name) + Logger.debug("Fetching cronjob", cron_job_name: name) K8s.Client.get("batch/v1", :cronjob, name: name, namespace: namespace) |> K8s.Client.put_conn(conn()) |> K8s.Client.run() diff --git a/lib/drowzee/k8s/cron_job.ex b/lib/drowzee/k8s/cron_job.ex index 28658d1..ae01172 100644 --- a/lib/drowzee/k8s/cron_job.ex +++ b/lib/drowzee/k8s/cron_job.ex @@ -14,12 +14,12 @@ defmodule Drowzee.K8s.CronJob do end def suspend_cron_job(%{"kind" => "CronJob"} = cron_job, suspend) do - Logger.info("Setting cron job suspend", cron_job: name(cron_job), suspend: suspend) + Logger.info("Setting cronjob suspend", cron_job: name(cron_job), suspend: suspend) cron_job = put_in(cron_job["spec"]["suspend"], suspend) case K8s.Client.run(Drowzee.K8s.conn(), K8s.Client.update(cron_job)) do {:ok, cron_job} -> {:ok, cron_job} {:error, reason} -> - Logger.error("Failed to suspend cron job: #{inspect(reason)}", cron_job: name(cron_job), suspend: suspend) + Logger.error("Failed to suspend cronjob: #{inspect(reason)}", cron_job: name(cron_job), suspend: suspend) {:error, reason} end end diff --git a/lib/drowzee/k8s/sleep_schedule.ex b/lib/drowzee/k8s/sleep_schedule.ex index 41e9503..59a6597 100644 --- a/lib/drowzee/k8s/sleep_schedule.ex +++ b/lib/drowzee/k8s/sleep_schedule.ex @@ -24,7 +24,7 @@ defmodule Drowzee.K8s.SleepSchedule do end def cron_job_names(sleep_schedule) do - (sleep_schedule["spec"]["cronJobs"] || []) + (sleep_schedule["spec"]["cronjobs"] || []) |> Enum.map(& &1["name"]) end @@ -125,7 +125,7 @@ defmodule Drowzee.K8s.SleepSchedule do end def suspend_cron_jobs(sleep_schedule) do - Logger.debug("Suspending cron jobs...") + Logger.debug("Suspending cronjobs...") case get_cron_jobs(sleep_schedule) do {:ok, cron_jobs} -> results = Enum.map(cron_jobs, &CronJob.suspend_cron_job(&1, true)) @@ -158,7 +158,7 @@ defmodule Drowzee.K8s.SleepSchedule do end def resume_cron_jobs(sleep_schedule) do - Logger.debug("Resuming cron jobs...") + Logger.debug("Resuming cronjobs...") case get_cron_jobs(sleep_schedule) do {:ok, cron_jobs} -> results = Enum.map(cron_jobs, &CronJob.suspend_cron_job(&1, false)) diff --git a/sleep_schedule.yaml b/sleep_schedule.yaml index 9ad150c..825d6fe 100644 --- a/sleep_schedule.yaml +++ b/sleep_schedule.yaml @@ -51,6 +51,6 @@ spec: - name: non-prod-service statefulsets: - name: non-prod-database - cronJobs: + cronjobs: - name: non-prod-backup ingressName: non-prod-service From d23d214ccf2bafef70bc8e3c512e740232ba0f3c Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 13:22:53 +0200 Subject: [PATCH 14/62] fix: Update web ui --- lib/drowzee_web/live/home_live/index.html.heex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/drowzee_web/live/home_live/index.html.heex b/lib/drowzee_web/live/home_live/index.html.heex index 7304f98..bbd6a38 100644 --- a/lib/drowzee_web/live/home_live/index.html.heex +++ b/lib/drowzee_web/live/home_live/index.html.heex @@ -164,7 +164,7 @@

📦 StatefulSets:

    - <%= for statefulset <- sleep_schedule["spec"]["statefulsets"] do %> + <%= for statefulset <- sleep_schedule["spec"]["statefulsets"] || [] do %>
  • 🚀 <%= statefulset["name"] %>
  • @@ -175,7 +175,7 @@

    📦 CronJobs:

      - <%= for cron_job <- sleep_schedule["spec"]["cronjobs"] do %> + <%= for cron_job <- sleep_schedule["spec"]["cronjobs"] || [] do %>
    • 🚀 <%= cron_job["name"] %>
    • From e16060e1b61d03d7b647df177990ccb4cf2abb55 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 13:30:12 +0200 Subject: [PATCH 15/62] fix: Update clusterrole --- chart/templates/clusterrole.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chart/templates/clusterrole.yaml b/chart/templates/clusterrole.yaml index 319d3ba..03a9085 100644 --- a/chart/templates/clusterrole.yaml +++ b/chart/templates/clusterrole.yaml @@ -38,8 +38,11 @@ rules: - '*' - resources: - deployments + - statefulsets + - cronjobs apiGroups: - "apps" + - "batch" verbs: - '*' - resources: From ef69056f51bd19eab5903a700ba0bbb281f03bb9 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 14:36:09 +0200 Subject: [PATCH 16/62] feat: Add logs --- lib/drowzee/k8s/sleep_schedule.ex | 3 +++ manifest.yaml | 26 +++++++++++++------------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/drowzee/k8s/sleep_schedule.ex b/lib/drowzee/k8s/sleep_schedule.ex index 59a6597..80a3aa4 100644 --- a/lib/drowzee/k8s/sleep_schedule.ex +++ b/lib/drowzee/k8s/sleep_schedule.ex @@ -14,16 +14,19 @@ defmodule Drowzee.K8s.SleepSchedule do end def deployment_names(sleep_schedule) do + Logger.debug("Deployment entries: #{inspect(sleep_schedule["spec"]["deployments"])}") (sleep_schedule["spec"]["deployments"] || []) |> Enum.map(& &1["name"]) end def statefulset_names(sleep_schedule) do + Logger.debug("Statefulsets entries: #{inspect(sleep_schedule["spec"]["statefulsets"])}") (sleep_schedule["spec"]["statefulsets"] || []) |> Enum.map(& &1["name"]) end def cron_job_names(sleep_schedule) do + Logger.debug("CronJobs entries: #{inspect(sleep_schedule["spec"]["cronjobs"])}") (sleep_schedule["spec"]["cronjobs"] || []) |> Enum.map(& &1["name"]) end diff --git a/manifest.yaml b/manifest.yaml index 2ea578c..1b0b030 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -34,8 +34,8 @@ spec: status: type: string enum: - - 'True' - - 'False' + - "True" + - "False" type: type: string reason: @@ -71,13 +71,13 @@ spec: description: The ingress that will be slept/woken. sleepTime: type: string - description: 'The time that the deployment will start sleeping(format: HH:MMam/pm)' + description: "The time that the deployment will start sleeping(format: HH:MMam/pm)" timezone: type: string description: The timezone that the input times are based in wakeTime: type: string - description: 'The time that the deployment will wake up (format: HH:MMam/pm)' + description: "The time that the deployment will wake up (format: HH:MMam/pm)" storage: true additionalPrinterColumns: - name: SleepTime @@ -130,50 +130,50 @@ rules: apiGroups: - apiextensions.k8s.io verbs: - - '*' + - "*" - resources: - leases apiGroups: - coordination.k8s.io verbs: - - '*' + - "*" - resources: - sleepschedules apiGroups: - drowzee.challengr.io verbs: - - '*' + - "*" - resources: - sleepschedules/status apiGroups: - drowzee.challengr.io verbs: - - '*' + - "*" - resources: - events apiGroups: - events.k8s.io verbs: - - '*' + - "*" - resources: - deployments apiGroups: - "apps" verbs: - - '*' + - "*" - resources: - ingresses apiGroups: - networking.k8s.io verbs: - - '*' + - "*" - resources: - services - - configmaps + - configmaps apiGroups: - "" verbs: - - '*' + - "*" --- metadata: From 5147bba7e6e13b4669255bb3c9c4a3bc9f5fbfcd Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 14:41:54 +0200 Subject: [PATCH 17/62] feat: Increase timeout to 30s --- lib/drowzee/transition_monitor.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/drowzee/transition_monitor.ex b/lib/drowzee/transition_monitor.ex index 1db000a..751e15b 100644 --- a/lib/drowzee/transition_monitor.ex +++ b/lib/drowzee/transition_monitor.ex @@ -14,7 +14,7 @@ defmodule Drowzee.TransitionMonitor do Logger.info("Monitoring transition...") task_name = "monitor_transition_#{name}_#{namespace}" |> String.to_atom() Bonny.PeriodicTask.unregister(task_name) - Bonny.PeriodicTask.new(task_name, {Drowzee.TransitionMonitor, :monitor_transition, [name, namespace, 1]}, 5000) + Bonny.PeriodicTask.new(task_name, {Drowzee.TransitionMonitor, :monitor_transition, [name, namespace, 1]}, 30000) end def monitor_transition(name, namespace, attempt) do From 795c2a75d6c564ff90a9fb84ebeec81807f65574 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 14:52:45 +0200 Subject: [PATCH 18/62] fix: Update logs --- config/config.exs | 2 +- lib/drowzee/controllers/sleep_schedule_controller.ex | 4 ++-- lib/drowzee/k8s.ex | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/config.exs b/config/config.exs index 5c30784..ea2a92e 100644 --- a/config/config.exs +++ b/config/config.exs @@ -48,7 +48,7 @@ config :tailwind, # Configures Elixir's Logger config :logger, :console, format: "$time [$level] $message $metadata\n", - metadata: [:name, :namespace, :deployment, :replicas, :ingress_name, :configmap_name, :request_id] + metadata: [:name, :namespace, :deployment, :statefulset, :cronjob, :replicas, :ingress_name, :configmap_name, :schedule_name, :schedule_namespace, :request_id] # Use Jason for JSON parsing in Phoenix config :phoenix, :json_library, Jason diff --git a/lib/drowzee/controllers/sleep_schedule_controller.ex b/lib/drowzee/controllers/sleep_schedule_controller.ex index e8edd52..3c16bbf 100644 --- a/lib/drowzee/controllers/sleep_schedule_controller.ex +++ b/lib/drowzee/controllers/sleep_schedule_controller.ex @@ -14,8 +14,8 @@ defmodule Drowzee.Controller.SleepScheduleController do def handle_event(%Bonny.Axn{action: action} = axn, _opts) when action in [:add, :modify, :reconcile] do Logger.metadata( - name: axn.resource["metadata"]["name"], - namespace: axn.resource["metadata"]["namespace"] + schedule_name: axn.resource["metadata"]["name"], + schedule_namespace: axn.resource["metadata"]["namespace"] ) axn |> add_default_conditions() diff --git a/lib/drowzee/k8s.ex b/lib/drowzee/k8s.ex index 81fe768..f7b82b8 100644 --- a/lib/drowzee/k8s.ex +++ b/lib/drowzee/k8s.ex @@ -90,21 +90,21 @@ defmodule Drowzee.K8s do end def get_deployment(name, namespace) do - Logger.debug("Fetching deployment", deployment_name: name) + Logger.debug("Fetching deployment", deployment: name) K8s.Client.get("apps/v1", :deployment, name: name, namespace: namespace) |> K8s.Client.put_conn(conn()) |> K8s.Client.run() end def get_statefulset(name, namespace) do - Logger.debug("Fetching statefulset", statefulset_name: name) + Logger.debug("Fetching statefulset", statefulset: name) K8s.Client.get("apps/v1", :statefulset, name: name, namespace: namespace) |> K8s.Client.put_conn(conn()) |> K8s.Client.run() end def get_cron_job(name, namespace) do - Logger.debug("Fetching cronjob", cron_job_name: name) + Logger.debug("Fetching cronjob", cron_job: name) K8s.Client.get("batch/v1", :cronjob, name: name, namespace: namespace) |> K8s.Client.put_conn(conn()) |> K8s.Client.run() From d145f2336c33f922b92a064ffb6748fc11169e25 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 22:01:02 +0200 Subject: [PATCH 19/62] fix: Update funxtion to check status_asleep --- .../controllers/sleep_schedule_controller.ex | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/drowzee/controllers/sleep_schedule_controller.ex b/lib/drowzee/controllers/sleep_schedule_controller.ex index 3c16bbf..85757e0 100644 --- a/lib/drowzee/controllers/sleep_schedule_controller.ex +++ b/lib/drowzee/controllers/sleep_schedule_controller.ex @@ -214,12 +214,16 @@ defmodule Drowzee.Controller.SleepScheduleController do end defp deployment_status_asleep?(status) do - # For deployments and statefulsets - if Map.has_key?(status, "replicas") do - Map.get(status, "replicas", 0) == 0 && Map.get(status, "readyReplicas", 0) == 0 - # For cronjobs - else - Map.get(status, "suspended", false) == true + replicas = Map.get(status, "replicas", 0) + ready = Map.get(status, "readyReplicas", 0) + suspended = Map.get(status, "suspended", nil) + + cond do + not is_nil(suspended) -> + suspended == true + + true -> + replicas == 0 and ready == 0 end end From 1d00a5221f462e9acfb3b5d759bf90c96b3ed6db Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 22:21:04 +0200 Subject: [PATCH 20/62] feat: Update logging --- config/config.exs | 2 +- .../controllers/sleep_schedule_controller.ex | 112 ++++++++++-------- lib/drowzee/k8s.ex | 10 +- lib/drowzee/k8s/cron_job.ex | 26 ---- lib/drowzee/k8s/cronjob.ex | 26 ++++ lib/drowzee/k8s/deployment.ex | 4 +- lib/drowzee/k8s/sleep_schedule.ex | 28 ++--- lib/drowzee/k8s/stateful_set.ex | 30 ----- lib/drowzee/k8s/statefuleset.ex | 22 ++++ .../live/home_live/index.html.heex | 4 +- 10 files changed, 135 insertions(+), 129 deletions(-) delete mode 100644 lib/drowzee/k8s/cron_job.ex create mode 100644 lib/drowzee/k8s/cronjob.ex delete mode 100644 lib/drowzee/k8s/stateful_set.ex create mode 100644 lib/drowzee/k8s/statefuleset.ex diff --git a/config/config.exs b/config/config.exs index ea2a92e..59d2638 100644 --- a/config/config.exs +++ b/config/config.exs @@ -48,7 +48,7 @@ config :tailwind, # Configures Elixir's Logger config :logger, :console, format: "$time [$level] $message $metadata\n", - metadata: [:name, :namespace, :deployment, :statefulset, :cronjob, :replicas, :ingress_name, :configmap_name, :schedule_name, :schedule_namespace, :request_id] + metadata: [:name, :namespace, :replicas, :configmap_name, :schedule_name, :request_id] # Use Jason for JSON parsing in Phoenix config :phoenix, :json_library, Jason diff --git a/lib/drowzee/controllers/sleep_schedule_controller.ex b/lib/drowzee/controllers/sleep_schedule_controller.ex index 85757e0..5157655 100644 --- a/lib/drowzee/controllers/sleep_schedule_controller.ex +++ b/lib/drowzee/controllers/sleep_schedule_controller.ex @@ -54,12 +54,12 @@ defmodule Drowzee.Controller.SleepScheduleController do sleep_time = resource["spec"]["sleepTime"] wake_time = resource["spec"]["wakeTime"] timezone = resource["spec"]["timezone"] - + # Log if wake_time is not defined (resources will remain down indefinitely) if is_nil(wake_time) or wake_time == "" do Logger.info("Wake time is not defined for #{resource["metadata"]["name"]} in namespace #{resource["metadata"]["namespace"]}. Resources will remain scaled down/suspended indefinitely.") end - + case Drowzee.SleepChecker.naptime?(sleep_time, wake_time, timezone) do {:ok, naptime} -> %{axn | assigns: Map.put(axn.assigns, :naptime, naptime)} @@ -136,14 +136,14 @@ defmodule Drowzee.Controller.SleepScheduleController do defp scale_down_deployments(axn) do SleepSchedule.scale_down_deployments(axn.resource) SleepSchedule.scale_down_statefulsets(axn.resource) - SleepSchedule.suspend_cron_jobs(axn.resource) + SleepSchedule.suspend_cronjobs(axn.resource) axn end defp scale_up_deployments(axn) do SleepSchedule.scale_up_deployments(axn.resource) SleepSchedule.scale_up_statefulsets(axn.resource) - SleepSchedule.resume_cron_jobs(axn.resource) + SleepSchedule.resume_cronjobs(axn.resource) axn end @@ -204,12 +204,17 @@ defmodule Drowzee.Controller.SleepScheduleController do end defp deployment_status_ready?(status) do - # For deployments and statefulsets - if Map.has_key?(status, "replicas") do - status["replicas"] != nil && status["readyReplicas"] != nil && status["replicas"] == status["readyReplicas"] - # For cronjobs - else - Map.get(status, "suspended", true) == false + replicas = Map.get(status, "replicas") + ready_replicas = Map.get(status, "readyReplicas") + suspended = Map.get(status, "suspended", true) + + cond do + not is_nil(replicas) and not is_nil(ready_replicas) -> + replicas == ready_replicas + + true -> + # For CronJobs: considered ready if not suspended + suspended == false end end @@ -230,41 +235,50 @@ defmodule Drowzee.Controller.SleepScheduleController do defp check_deployment_status(axn, check_fn) do with {:ok, deployments} <- SleepSchedule.get_deployments(axn.resource), {:ok, statefulsets} <- SleepSchedule.get_statefulsets(axn.resource), - {:ok, cron_jobs} <- SleepSchedule.get_cron_jobs(axn.resource) do - - deployments_result = Enum.all?(deployments, fn deployment -> - Logger.debug("Deployment #{Deployment.name(deployment)} replicas: #{Deployment.replicas(deployment)}, readyReplicas: #{Deployment.ready_replicas(deployment)}") - check_fn.(deployment["status"]) - end) - - statefulsets_result = Enum.all?(statefulsets, fn statefulset -> - Logger.debug("StatefulSet #{StatefulSet.name(statefulset)} replicas: #{StatefulSet.replicas(statefulset)}, readyReplicas: #{StatefulSet.ready_replicas(statefulset)}") - check_fn.(statefulset["status"]) - end) - - cron_jobs_result = Enum.all?(cron_jobs, fn cron_job -> - suspended = CronJob.suspend(cron_job) - Logger.debug("CronJob #{CronJob.name(cron_job)} suspended: #{suspended}") - # For sleeping, we want all jobs to be suspended (true) - # For waking, we want all jobs to not be suspended (false) - # check_fn will determine which state we're checking for + {:ok, cronjobs} <- SleepSchedule.get_cronjobs(axn.resource) do + + deployments_result = + Enum.all?(deployments, fn deployment -> + Logger.debug( + "Deployment #{Deployment.name(deployment)} replicas: #{Deployment.replicas(deployment)}, readyReplicas: #{Deployment.ready_replicas(deployment)}" + ) + + check_fn.(deployment["status"]) + end) + + statefulsets_result = + Enum.all?(statefulsets, fn statefulset -> + Logger.debug( + "StatefulSet #{StatefulSet.name(statefulset)} replicas: #{StatefulSet.replicas(statefulset)}, readyReplicas: #{StatefulSet.ready_replicas(statefulset)}" + ) + + check_fn.(statefulset["status"]) + end) + + cronjobs_result = + Enum.all?(cronjobs, fn cronjob -> + suspended = CronJob.suspend(cronjob) + + Logger.debug("CronJob #{CronJob.name(cronjob)} suspended: #{suspended}") + + # check_fn determines if suspended is the expected state (true for sleep, false for wake) check_fn.(%{"suspended" => suspended}) end) - + # Determine which resources we need to check based on what's present - has_deployments = !Enum.empty?(deployments) - has_statefulsets = !Enum.empty?(statefulsets) - has_cron_jobs = !Enum.empty?(cron_jobs) - - cond do - !has_deployments && !has_statefulsets && !has_cron_jobs -> {:ok, true} # No resources to check - has_deployments && !has_statefulsets && !has_cron_jobs -> {:ok, deployments_result} - !has_deployments && has_statefulsets && !has_cron_jobs -> {:ok, statefulsets_result} - !has_deployments && !has_statefulsets && has_cron_jobs -> {:ok, cron_jobs_result} - has_deployments && has_statefulsets && !has_cron_jobs -> {:ok, deployments_result && statefulsets_result} - has_deployments && !has_statefulsets && has_cron_jobs -> {:ok, deployments_result && cron_jobs_result} - !has_deployments && has_statefulsets && has_cron_jobs -> {:ok, statefulsets_result && cron_jobs_result} - true -> {:ok, deployments_result && statefulsets_result && cron_jobs_result} # Check all three + has_deployments = Enum.any?(deployments) + has_statefulsets = Enum.any?(statefulsets) + has_cronjobs = Enum.any?(cronjobs) + + case {has_deployments, has_statefulsets, has_cronjobs} do + {false, false, false} -> {:ok, true} + {true, false, false} -> {:ok, deployments_result} + {false, true, false} -> {:ok, statefulsets_result} + {false, false, true} -> {:ok, cronjobs_result} + {true, true, false} -> {:ok, deployments_result && statefulsets_result} + {true, false, true} -> {:ok, deployments_result && cronjobs_result} + {false, true, true} -> {:ok, statefulsets_result && cronjobs_result} + {true, true, true} -> {:ok, deployments_result && statefulsets_result && cronjobs_result} end else {:error, error} -> {:error, error} @@ -276,19 +290,19 @@ defmodule Drowzee.Controller.SleepScheduleController do manual_override = Keyword.get(opts, :manual_override, false) wake_time = axn.resource["spec"]["wakeTime"] permanent_sleep = is_nil(wake_time) or wake_time == "" - + sleep_reason = cond do manual_override -> "ManualSleep" permanent_sleep -> "PermanentSleep" true -> "ScheduledSleep" end - + sleep_message = if permanent_sleep do "Deployments, StatefulSets, and CronJobs have been scaled down/suspended indefinitely and ingress redirected." else "Deployments, StatefulSets, and CronJobs have been scaled down/suspended and ingress redirected." end - + axn |> set_condition("Transitioning", false, "NoTransition", "No transition in progress") |> set_condition("Sleeping", true, sleep_reason, sleep_message) @@ -308,14 +322,14 @@ defmodule Drowzee.Controller.SleepScheduleController do defp put_ingress_to_sleep(axn) do case SleepSchedule.put_ingress_to_sleep(axn.resource) do {:ok, _} -> - Logger.info("Updated ingress to redirect to Drowzee", ingress_name: SleepSchedule.ingress_name(axn.resource)) + Logger.info("Updated ingress to redirect to Drowzee", name: SleepSchedule.ingress_name(axn.resource)) # register_event(axn, nil, :Normal, "SleepIngress", "Ingress has been redirected to Drowzee") axn {:error, :ingress_name_not_set} -> Logger.info("No ingressName has been provided so there is nothing to redirect") axn {:error, error} -> - Logger.error("Failed to redirect ingress to Drowzee: #{inspect(error)}", ingress_name: SleepSchedule.ingress_name(axn.resource)) + Logger.error("Failed to redirect ingress to Drowzee: #{inspect(error)}", name: SleepSchedule.ingress_name(axn.resource)) axn end end @@ -323,14 +337,14 @@ defmodule Drowzee.Controller.SleepScheduleController do defp wake_up_ingress(axn) do case SleepSchedule.wake_up_ingress(axn.resource) do {:ok, _} -> - Logger.info("Removed Drowzee redirect from ingress", ingress_name: SleepSchedule.ingress_name(axn.resource)) + Logger.info("Removed Drowzee redirect from ingress", name: SleepSchedule.ingress_name(axn.resource)) # register_event(axn, nil, :Normal, "WakeUpIngress", "Ingress has been restored") axn {:error, :ingress_name_not_set} -> Logger.info("No ingressName has been provided so there is nothing to restore") axn {:error, error} -> - Logger.error("Failed to remove Drowzee redirect from ingress: #{inspect(error)}", ingress_name: SleepSchedule.ingress_name(axn.resource)) + Logger.error("Failed to remove Drowzee redirect from ingress: #{inspect(error)}", name: SleepSchedule.ingress_name(axn.resource)) axn end end diff --git a/lib/drowzee/k8s.ex b/lib/drowzee/k8s.ex index f7b82b8..29131ad 100644 --- a/lib/drowzee/k8s.ex +++ b/lib/drowzee/k8s.ex @@ -73,7 +73,7 @@ defmodule Drowzee.K8s do def get_drowzee_ingress() do name = "drowzee" namespace = drowzee_namespace() - Logger.debug("Fetching drowzee ingress", ingress_name: name, drowzee_namespace: namespace) + Logger.debug("Fetching drowzee ingress", name: name, drowzee_namespace: namespace) K8s.Client.get("networking.k8s.io/v1", :ingress, name: name, namespace: namespace) |> K8s.Client.put_conn(conn()) |> K8s.Client.run() @@ -90,21 +90,21 @@ defmodule Drowzee.K8s do end def get_deployment(name, namespace) do - Logger.debug("Fetching deployment", deployment: name) + Logger.debug("Fetching deployment", name: name) K8s.Client.get("apps/v1", :deployment, name: name, namespace: namespace) |> K8s.Client.put_conn(conn()) |> K8s.Client.run() end def get_statefulset(name, namespace) do - Logger.debug("Fetching statefulset", statefulset: name) + Logger.debug("Fetching statefulset", name: name) K8s.Client.get("apps/v1", :statefulset, name: name, namespace: namespace) |> K8s.Client.put_conn(conn()) |> K8s.Client.run() end - def get_cron_job(name, namespace) do - Logger.debug("Fetching cronjob", cron_job: name) + def get_cronjob(name, namespace) do + Logger.debug("Fetching cronjob", name: name) K8s.Client.get("batch/v1", :cronjob, name: name, namespace: namespace) |> K8s.Client.put_conn(conn()) |> K8s.Client.run() diff --git a/lib/drowzee/k8s/cron_job.ex b/lib/drowzee/k8s/cron_job.ex deleted file mode 100644 index ae01172..0000000 --- a/lib/drowzee/k8s/cron_job.ex +++ /dev/null @@ -1,26 +0,0 @@ -defmodule Drowzee.K8s.CronJob do - require Logger - - def name(cron_job) do - cron_job["metadata"]["name"] - end - - def namespace(cron_job) do - cron_job["metadata"]["namespace"] - end - - def suspend(cron_job) do - cron_job["spec"]["suspend"] || false - end - - def suspend_cron_job(%{"kind" => "CronJob"} = cron_job, suspend) do - Logger.info("Setting cronjob suspend", cron_job: name(cron_job), suspend: suspend) - cron_job = put_in(cron_job["spec"]["suspend"], suspend) - case K8s.Client.run(Drowzee.K8s.conn(), K8s.Client.update(cron_job)) do - {:ok, cron_job} -> {:ok, cron_job} - {:error, reason} -> - Logger.error("Failed to suspend cronjob: #{inspect(reason)}", cron_job: name(cron_job), suspend: suspend) - {:error, reason} - end - end -end diff --git a/lib/drowzee/k8s/cronjob.ex b/lib/drowzee/k8s/cronjob.ex new file mode 100644 index 0000000..10b7f02 --- /dev/null +++ b/lib/drowzee/k8s/cronjob.ex @@ -0,0 +1,26 @@ +defmodule Drowzee.K8s.CronJob do + require Logger + + def name(cronjob) do + cronjob["metadata"]["name"] + end + + def namespace(cronjob) do + cronjob["metadata"]["namespace"] + end + + def suspend(cronjob) do + cronjob["spec"]["suspend"] || false + end + + def suspend_cronjob(%{"kind" => "CronJob"} = cronjob, suspend) do + Logger.info("Setting cronjob suspend", name: name(cronjob), suspend: suspend) + cronjob = put_in(cronjob["spec"]["suspend"], suspend) + case K8s.Client.run(Drowzee.K8s.conn(), K8s.Client.update(cronjob)) do + {:ok, cronjob} -> {:ok, cronjob} + {:error, reason} -> + Logger.error("Failed to suspend cronjob: #{inspect(reason)}", name: name(cronjob), suspend: suspend) + {:error, reason} + end + end +end diff --git a/lib/drowzee/k8s/deployment.ex b/lib/drowzee/k8s/deployment.ex index 59bca33..fd7b940 100644 --- a/lib/drowzee/k8s/deployment.ex +++ b/lib/drowzee/k8s/deployment.ex @@ -18,12 +18,12 @@ defmodule Drowzee.K8s.Deployment do end def scale_deployment(%{"kind" => "Deployment"} = deployment, replicas) do - Logger.info("Scaling deployment", deployment: name(deployment), replicas: replicas) + Logger.info("Scaling deployment", name: name(deployment), replicas: replicas) deployment = put_in(deployment["spec"]["replicas"], replicas) case K8s.Client.run(Drowzee.K8s.conn(), K8s.Client.update(deployment)) do {:ok, deployment} -> {:ok, deployment} {:error, reason} -> - Logger.error("Failed to scale deployment: #{inspect(reason)}", deployment: name(deployment), replicas: replicas) + Logger.error("Failed to scale deployment: #{inspect(reason)}", name: name(deployment), replicas: replicas) {:error, reason} end end diff --git a/lib/drowzee/k8s/sleep_schedule.ex b/lib/drowzee/k8s/sleep_schedule.ex index 80a3aa4..d48e024 100644 --- a/lib/drowzee/k8s/sleep_schedule.ex +++ b/lib/drowzee/k8s/sleep_schedule.ex @@ -25,7 +25,7 @@ defmodule Drowzee.K8s.SleepSchedule do |> Enum.map(& &1["name"]) end - def cron_job_names(sleep_schedule) do + def cronjob_names(sleep_schedule) do Logger.debug("CronJobs entries: #{inspect(sleep_schedule["spec"]["cronjobs"])}") (sleep_schedule["spec"]["cronjobs"] || []) |> Enum.map(& &1["name"]) @@ -59,7 +59,7 @@ defmodule Drowzee.K8s.SleepSchedule do "" -> {:error, :ingress_name_not_set} ingress_name -> namespace = namespace(sleep_schedule) - Logger.debug("Fetching ingress", ingress_name: ingress_name) + Logger.debug("Fetching ingress", name: ingress_name) K8s.Client.get("networking.k8s.io/v1", :ingress, name: ingress_name, namespace: namespace) |> K8s.Client.put_conn(Drowzee.K8s.conn()) |> K8s.Client.run() @@ -93,14 +93,14 @@ defmodule Drowzee.K8s.SleepSchedule do end @retry with: exponential_backoff(1000) |> Stream.take(2) - def get_cron_jobs(sleep_schedule) do + def get_cronjobs(sleep_schedule) do namespace = namespace(sleep_schedule) - results = (cron_job_names(sleep_schedule) || []) - |> Stream.map(&Drowzee.K8s.get_cron_job(&1, namespace)) + results = (cronjob_names(sleep_schedule) || []) + |> Stream.map(&Drowzee.K8s.get_cronjob(&1, namespace)) |> Enum.to_list() case Enum.all?(results, fn {:ok, _} -> true; _ -> false end) do - true -> {:ok, Enum.map(results, fn {:ok, cron_job} -> cron_job end)} + true -> {:ok, Enum.map(results, fn {:ok, cronjob} -> cronjob end)} false -> {:error, Enum.filter(results, fn {:error, _} -> true; _ -> false end) |> Enum.map(fn {:error, error} -> error end)} end end @@ -127,11 +127,11 @@ defmodule Drowzee.K8s.SleepSchedule do end end - def suspend_cron_jobs(sleep_schedule) do + def suspend_cronjobs(sleep_schedule) do Logger.debug("Suspending cronjobs...") - case get_cron_jobs(sleep_schedule) do - {:ok, cron_jobs} -> - results = Enum.map(cron_jobs, &CronJob.suspend_cron_job(&1, true)) + case get_cronjobs(sleep_schedule) do + {:ok, cronjobs} -> + results = Enum.map(cronjobs, &CronJob.suspend_cronjob(&1, true)) {:ok, results} {:error, error} -> {:error, error} @@ -160,11 +160,11 @@ defmodule Drowzee.K8s.SleepSchedule do end end - def resume_cron_jobs(sleep_schedule) do + def resume_cronjobs(sleep_schedule) do Logger.debug("Resuming cronjobs...") - case get_cron_jobs(sleep_schedule) do - {:ok, cron_jobs} -> - results = Enum.map(cron_jobs, &CronJob.suspend_cron_job(&1, false)) + case get_cronjobs(sleep_schedule) do + {:ok, cronjobs} -> + results = Enum.map(cronjobs, &CronJob.suspend_cronjob(&1, false)) {:ok, results} {:error, error} -> {:error, error} diff --git a/lib/drowzee/k8s/stateful_set.ex b/lib/drowzee/k8s/stateful_set.ex deleted file mode 100644 index 08598d6..0000000 --- a/lib/drowzee/k8s/stateful_set.ex +++ /dev/null @@ -1,30 +0,0 @@ -defmodule Drowzee.K8s.StatefulSet do - require Logger - - def name(statefulset) do - statefulset["metadata"]["name"] - end - - def namespace(statefulset) do - statefulset["metadata"]["namespace"] - end - - def replicas(statefulset) do - statefulset["spec"]["replicas"] || 0 - end - - def ready_replicas(statefulset) do - statefulset["status"]["readyReplicas"] || 0 - end - - def scale_statefulset(%{"kind" => "StatefulSet"} = statefulset, replicas) do - Logger.info("Scaling statefulset", statefulset: name(statefulset), replicas: replicas) - statefulset = put_in(statefulset["spec"]["replicas"], replicas) - case K8s.Client.run(Drowzee.K8s.conn(), K8s.Client.update(statefulset)) do - {:ok, statefulset} -> {:ok, statefulset} - {:error, reason} -> - Logger.error("Failed to scale statefulset: #{inspect(reason)}", statefulset: name(statefulset), replicas: replicas) - {:error, reason} - end - end -end diff --git a/lib/drowzee/k8s/statefuleset.ex b/lib/drowzee/k8s/statefuleset.ex new file mode 100644 index 0000000..cd9abd5 --- /dev/null +++ b/lib/drowzee/k8s/statefuleset.ex @@ -0,0 +1,22 @@ +defmodule Drowzee.K8s.StatefulSet do + require Logger + + def name(statefulset), do: statefulset["metadata"]["name"] + + def namespace(statefulset), do: statefulset["metadata"]["namespace"] + + def replicas(statefulset), do: statefulset["spec"]["replicas"] || 0 + + def ready_replicas(statefulset), do: statefulset["status"]["readyReplicas"] || 0 + + def scale_statefulset(%{"kind" => "StatefulSet"} = statefulset, replicas) do + Logger.info("Scaling statefulset", name: name(statefulset), replicas: replicas) + statefulset = put_in(statefulset["spec"]["replicas"], replicas) + case K8s.Client.run(Drowzee.K8s.conn(), K8s.Client.update(statefulset)) do + {:ok, statefulset} -> {:ok, statefulset} + {:error, reason} -> + Logger.error("Failed to scale statefulset: #{inspect(reason)}", name: name(statefulset), replicas: replicas) + {:error, reason} + end + end +end diff --git a/lib/drowzee_web/live/home_live/index.html.heex b/lib/drowzee_web/live/home_live/index.html.heex index bbd6a38..e108d1f 100644 --- a/lib/drowzee_web/live/home_live/index.html.heex +++ b/lib/drowzee_web/live/home_live/index.html.heex @@ -175,9 +175,9 @@

      📦 CronJobs:

        - <%= for cron_job <- sleep_schedule["spec"]["cronjobs"] || [] do %> + <%= for cronjob <- sleep_schedule["spec"]["cronjobs"] || [] do %>
      • - 🚀 <%= cron_job["name"] %> + 🚀 <%= cronjob["name"] %>
      • <% end %>
      From 68573923d93f982fabe84893f6b35fe05db615e7 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 22:22:38 +0200 Subject: [PATCH 21/62] feat: Small fixes --- lib/drowzee/k8s/cronjob.ex | 12 +++--------- lib/drowzee/k8s/deployment.ex | 16 ++++------------ 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/lib/drowzee/k8s/cronjob.ex b/lib/drowzee/k8s/cronjob.ex index 10b7f02..7cae0df 100644 --- a/lib/drowzee/k8s/cronjob.ex +++ b/lib/drowzee/k8s/cronjob.ex @@ -1,17 +1,11 @@ defmodule Drowzee.K8s.CronJob do require Logger - def name(cronjob) do - cronjob["metadata"]["name"] - end + def name(cronjob), do: cronjob["metadata"]["name"] - def namespace(cronjob) do - cronjob["metadata"]["namespace"] - end + def namespace(cronjob), do: cronjob["metadata"]["namespace"] - def suspend(cronjob) do - cronjob["spec"]["suspend"] || false - end + def suspend(cronjob), do: cronjob["spec"]["suspend"] || false def suspend_cronjob(%{"kind" => "CronJob"} = cronjob, suspend) do Logger.info("Setting cronjob suspend", name: name(cronjob), suspend: suspend) diff --git a/lib/drowzee/k8s/deployment.ex b/lib/drowzee/k8s/deployment.ex index fd7b940..ebd8943 100644 --- a/lib/drowzee/k8s/deployment.ex +++ b/lib/drowzee/k8s/deployment.ex @@ -1,21 +1,13 @@ defmodule Drowzee.K8s.Deployment do require Logger - def name(deployment) do - deployment["metadata"]["name"] - end + def name(deployment), do: deployment["metadata"]["name"] - def namespace(deployment) do - deployment["metadata"]["namespace"] - end + def namespace(deployment), do: deployment["metadata"]["namespace"] - def replicas(deployment) do - deployment["spec"]["replicas"] || 0 - end + def replicas(deployment), do: deployment["spec"]["replicas"] || 0 - def ready_replicas(deployment) do - deployment["status"]["readyReplicas"] || 0 - end + def ready_replicas(deployment), do: deployment["status"]["readyReplicas"] || 0 def scale_deployment(%{"kind" => "Deployment"} = deployment, replicas) do Logger.info("Scaling deployment", name: name(deployment), replicas: replicas) From 4191dab0c1a5ead0de1300b1862c6bf1c4e16a41 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 22:40:48 +0200 Subject: [PATCH 22/62] feat: Save original replicas in annotations --- lib/drowzee/k8s/deployment.ex | 23 +++++++++++++++++++++++ lib/drowzee/k8s/sleep_schedule.ex | 23 +++++++++++++++++++---- lib/drowzee/k8s/statefuleset.ex | 23 +++++++++++++++++++++++ 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/lib/drowzee/k8s/deployment.ex b/lib/drowzee/k8s/deployment.ex index ebd8943..9edacb1 100644 --- a/lib/drowzee/k8s/deployment.ex +++ b/lib/drowzee/k8s/deployment.ex @@ -9,6 +9,29 @@ defmodule Drowzee.K8s.Deployment do def ready_replicas(deployment), do: deployment["status"]["readyReplicas"] || 0 + @doc """ + Always save (or update) the current replica count as an annotation on the deployment. + """ + def save_original_replicas(deployment) do + current = get_in(deployment, ["spec", "replicas"]) || 0 + put_in(deployment, ["metadata", "annotations", "drowzee.chirpwireless.io/original-replicas"], Integer.to_string(current)) + end + + @doc """ + Get the original replicas count from the annotation, as integer. Default to 1 if missing or invalid. + """ + def get_original_replicas(deployment) do + annotations = get_in(deployment, ["metadata", "annotations"]) || %{} + case Map.get(annotations, "drowzee.chirpwireless.io/original-replicas") do + nil -> 1 + value -> + case Integer.parse(value) do + {count, _} -> count + :error -> 1 + end + end + end + def scale_deployment(%{"kind" => "Deployment"} = deployment, replicas) do Logger.info("Scaling deployment", name: name(deployment), replicas: replicas) deployment = put_in(deployment["spec"]["replicas"], replicas) diff --git a/lib/drowzee/k8s/sleep_schedule.ex b/lib/drowzee/k8s/sleep_schedule.ex index d48e024..7f78196 100644 --- a/lib/drowzee/k8s/sleep_schedule.ex +++ b/lib/drowzee/k8s/sleep_schedule.ex @@ -109,7 +109,11 @@ defmodule Drowzee.K8s.SleepSchedule do Logger.debug("Scaling down deployments...") case get_deployments(sleep_schedule) do {:ok, deployments} -> - results = Enum.map(deployments, &Deployment.scale_deployment(&1, 0)) + results = + Enum.map(deployments, fn deployment -> + deployment = Deployment.save_original_replicas(deployment) + Deployment.scale_deployment(deployment, 0) + end) {:ok, results} {:error, error} -> {:error, error} @@ -120,7 +124,11 @@ defmodule Drowzee.K8s.SleepSchedule do Logger.debug("Scaling down statefulsets...") case get_statefulsets(sleep_schedule) do {:ok, statefulsets} -> - results = Enum.map(statefulsets, &StatefulSet.scale_statefulset(&1, 0)) + results = + Enum.map(statefulsets, fn statefulset -> + statefulset = Statefulset.save_original_replicas(statefulset) + Statefulset.scale_statefulset(statefulset, 0) + end) {:ok, results} {:error, error} -> {:error, error} @@ -142,7 +150,10 @@ defmodule Drowzee.K8s.SleepSchedule do Logger.debug("Scaling up deployments...") case get_deployments(sleep_schedule) do {:ok, deployments} -> - results = Enum.map(deployments, &Deployment.scale_deployment(&1, 1)) + results = Enum.map(deployments, fn deployment -> + original = Deployment.get_original_replicas(deployment) + Deployment.scale_deployment(deployment, original) + end) {:ok, results} {:error, error} -> {:error, error} @@ -153,7 +164,11 @@ defmodule Drowzee.K8s.SleepSchedule do Logger.debug("Scaling up statefulsets...") case get_statefulsets(sleep_schedule) do {:ok, statefulsets} -> - results = Enum.map(statefulsets, &StatefulSet.scale_statefulset(&1, 1)) + results = + Enum.map(statefulsets, fn statefulset -> + original = Statefulset.get_original_replicas(statefulset) + Statefulset.scale_statefulset(statefulset, original) + end) {:ok, results} {:error, error} -> {:error, error} diff --git a/lib/drowzee/k8s/statefuleset.ex b/lib/drowzee/k8s/statefuleset.ex index cd9abd5..d36a2d4 100644 --- a/lib/drowzee/k8s/statefuleset.ex +++ b/lib/drowzee/k8s/statefuleset.ex @@ -9,6 +9,29 @@ defmodule Drowzee.K8s.StatefulSet do def ready_replicas(statefulset), do: statefulset["status"]["readyReplicas"] || 0 + @doc """ + Always save (or update) the current replica count as an annotation on the statefulset. + """ + def save_original_replicas(statefulset) do + current = get_in(statefulset, ["spec", "replicas"]) || 0 + put_in(statefulset, ["metadata", "annotations", "drowzee.chirpwireless.io/original-replicas"], Integer.to_string(current)) + end + + @doc """ + Get the original replicas count from the annotation, as integer. Default to 1 if missing or invalid. + """ + def get_original_replicas(statefulset) do + annotations = get_in(statefulset, ["metadata", "annotations"]) || %{} + case Map.get(annotations, "drowzee.chirpwireless.io/original-replicas") do + nil -> 1 + value -> + case Integer.parse(value) do + {count, _} -> count + :error -> 1 + end + end + end + def scale_statefulset(%{"kind" => "StatefulSet"} = statefulset, replicas) do Logger.info("Scaling statefulset", name: name(statefulset), replicas: replicas) statefulset = put_in(statefulset["spec"]["replicas"], replicas) From c610204eac0ed93cbac6684c5749004a0d0061c8 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 22:43:51 +0200 Subject: [PATCH 23/62] fix: Update logs metadata --- config/config.exs | 2 +- lib/drowzee/controllers/sleep_schedule_controller.ex | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/config.exs b/config/config.exs index 59d2638..983d09b 100644 --- a/config/config.exs +++ b/config/config.exs @@ -48,7 +48,7 @@ config :tailwind, # Configures Elixir's Logger config :logger, :console, format: "$time [$level] $message $metadata\n", - metadata: [:name, :namespace, :replicas, :configmap_name, :schedule_name, :request_id] + metadata: [:name, :namespace, :replicas, :configmap_name, :schedule, :request_id] # Use Jason for JSON parsing in Phoenix config :phoenix, :json_library, Jason diff --git a/lib/drowzee/controllers/sleep_schedule_controller.ex b/lib/drowzee/controllers/sleep_schedule_controller.ex index 5157655..5e4ed88 100644 --- a/lib/drowzee/controllers/sleep_schedule_controller.ex +++ b/lib/drowzee/controllers/sleep_schedule_controller.ex @@ -14,8 +14,8 @@ defmodule Drowzee.Controller.SleepScheduleController do def handle_event(%Bonny.Axn{action: action} = axn, _opts) when action in [:add, :modify, :reconcile] do Logger.metadata( - schedule_name: axn.resource["metadata"]["name"], - schedule_namespace: axn.resource["metadata"]["namespace"] + schedule: axn.resource["metadata"]["name"], + namespace: axn.resource["metadata"]["namespace"] ) axn |> add_default_conditions() From b5fe4b2daf33a6d732a638822da0c325cf883b39 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 22:48:06 +0200 Subject: [PATCH 24/62] fix: Update StatefulSet module call --- lib/drowzee/k8s/sleep_schedule.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/drowzee/k8s/sleep_schedule.ex b/lib/drowzee/k8s/sleep_schedule.ex index 7f78196..7b65f85 100644 --- a/lib/drowzee/k8s/sleep_schedule.ex +++ b/lib/drowzee/k8s/sleep_schedule.ex @@ -126,8 +126,8 @@ defmodule Drowzee.K8s.SleepSchedule do {:ok, statefulsets} -> results = Enum.map(statefulsets, fn statefulset -> - statefulset = Statefulset.save_original_replicas(statefulset) - Statefulset.scale_statefulset(statefulset, 0) + statefulset = StatefulSet.save_original_replicas(statefulset) + StatefulSet.scale_statefulset(statefulset, 0) end) {:ok, results} {:error, error} -> @@ -166,8 +166,8 @@ defmodule Drowzee.K8s.SleepSchedule do {:ok, statefulsets} -> results = Enum.map(statefulsets, fn statefulset -> - original = Statefulset.get_original_replicas(statefulset) - Statefulset.scale_statefulset(statefulset, original) + original = StatefulSet.get_original_replicas(statefulset) + StatefulSet.scale_statefulset(statefulset, original) end) {:ok, results} {:error, error} -> From 067c8c576bcecd0070937bf3f2a50e9044cd470c Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 22:59:53 +0200 Subject: [PATCH 25/62] feat: Update status in ui --- .../live/home_live/index.html.heex | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/drowzee_web/live/home_live/index.html.heex b/lib/drowzee_web/live/home_live/index.html.heex index e108d1f..f87178c 100644 --- a/lib/drowzee_web/live/home_live/index.html.heex +++ b/lib/drowzee_web/live/home_live/index.html.heex @@ -155,7 +155,12 @@
        <%= for deployment <- sleep_schedule["spec"]["deployments"] do %>
      • - 🚀 <%= deployment["name"] %> + <%= if d && (Map.get(d["spec"], "replicas", 0) > 0) do %> + 🚀 + <% else %> + 💤 + <% end %> + <%= deployment["name"] %>
      • <% end %>
      @@ -166,7 +171,12 @@
        <%= for statefulset <- sleep_schedule["spec"]["statefulsets"] || [] do %>
      • - 🚀 <%= statefulset["name"] %> + <%= if d && (Map.get(d["spec"], "replicas", 0) > 0) do %> + 🚀 + <% else %> + 💤 + <% end %> + <%= statefulset["name"] %>
      • <% end %>
      @@ -177,7 +187,12 @@
        <%= for cronjob <- sleep_schedule["spec"]["cronjobs"] || [] do %>
      • - 🚀 <%= cronjob["name"] %> + <%= if c && Map.get(c["spec"], "suspend", false) == false do %> + 🚀 + <% else %> + 💤 + <% end %> + <%= cronjob["name"] %>
      • <% end %>
      From fac07a1be4125ebb8040b5f502d84a0cf486b1c2 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 23:16:12 +0200 Subject: [PATCH 26/62] feat: Update web ui --- lib/drowzee_web/live/home_live/index.ex | 35 +++++++++++++++++++ .../live/home_live/index.html.heex | 5 ++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/drowzee_web/live/home_live/index.ex b/lib/drowzee_web/live/home_live/index.ex index 666e86a..08948d2 100644 --- a/lib/drowzee_web/live/home_live/index.ex +++ b/lib/drowzee_web/live/home_live/index.ex @@ -184,8 +184,43 @@ defmodule DrowzeeWeb.HomeLive.Index do [Drowzee.K8s.get_sleep_schedule!(name, socket.assigns.namespace)] end + # Gather all deployments for all schedules + deployments = + sleep_schedules + |> Enum.flat_map(fn schedule -> + case Drowzee.K8s.SleepSchedule.get_deployments(schedule) do + {:ok, ds} -> ds + {:error, _} -> [] + end + end) + + statefulsets = + sleep_schedules + |> Enum.flat_map(fn schedule -> + case Drowzee.K8s.SleepSchedule.get_statefulsets(schedule) do + {:ok, ss} -> ss + {:error, _} -> [] + end + end) + + cronjobs = + sleep_schedules + |> Enum.flat_map(fn schedule -> + case Drowzee.K8s.SleepSchedule.get_cronjobs(schedule) do + {:ok, cs} -> cs + {:error, _} -> [] + end + end) + + deployments_by_name = Map.new(deployments, &{&1["metadata"]["name"], &1}) + statefulsets_by_name = Map.new(statefulsets, &{&1["metadata"]["name"], &1}) + cronjobs_by_name = Map.new(cronjobs, &{&1["metadata"]["name"], &1}) + socket |> assign(:sleep_schedules, sleep_schedules) + |> assign(:deployments_by_name, deployments_by_name) + |> assign(:statefulsets_by_name, statefulsets_by_name) + |> assign(:cronjobs_by_name, cronjobs_by_name) |> filter_sleep_schedules(socket.assigns.search) end diff --git a/lib/drowzee_web/live/home_live/index.html.heex b/lib/drowzee_web/live/home_live/index.html.heex index f87178c..484568a 100644 --- a/lib/drowzee_web/live/home_live/index.html.heex +++ b/lib/drowzee_web/live/home_live/index.html.heex @@ -155,6 +155,7 @@
        <%= for deployment <- sleep_schedule["spec"]["deployments"] do %>
      • + <% d = @deployments_by_name[deployment["name"]] %> <%= if d && (Map.get(d["spec"], "replicas", 0) > 0) do %> 🚀 <% else %> @@ -171,7 +172,8 @@
          <%= for statefulset <- sleep_schedule["spec"]["statefulsets"] || [] do %>
        • - <%= if d && (Map.get(d["spec"], "replicas", 0) > 0) do %> + <% s = @statefulsets_by_name[statefulset["name"]] %> + <%= if s && (Map.get(d["spec"], "replicas", 0) > 0) do %> 🚀 <% else %> 💤 @@ -187,6 +189,7 @@
            <%= for cronjob <- sleep_schedule["spec"]["cronjobs"] || [] do %>
          • + <% c = @cronjobs_by_name[cronjob["name"]] %> <%= if c && Map.get(c["spec"], "suspend", false) == false do %> 🚀 <% else %> From 9e4340c83146b372ed69409f2ef0e8b5cbefaefc Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 23:24:42 +0200 Subject: [PATCH 27/62] fix: Fix web ui --- lib/drowzee_web/live/home_live/index.html.heex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/drowzee_web/live/home_live/index.html.heex b/lib/drowzee_web/live/home_live/index.html.heex index 484568a..c8b896a 100644 --- a/lib/drowzee_web/live/home_live/index.html.heex +++ b/lib/drowzee_web/live/home_live/index.html.heex @@ -173,7 +173,7 @@ <%= for statefulset <- sleep_schedule["spec"]["statefulsets"] || [] do %>
          • <% s = @statefulsets_by_name[statefulset["name"]] %> - <%= if s && (Map.get(d["spec"], "replicas", 0) > 0) do %> + <%= if s && (Map.get(s["spec"], "replicas", 0) > 0) do %> 🚀 <% else %> 💤 From 43dea7ca0821a20011683ce06f20a4729e3adf47 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 23:34:13 +0200 Subject: [PATCH 28/62] feat: Rename annotation --- lib/drowzee/k8s/deployment.ex | 4 ++-- lib/drowzee/k8s/statefuleset.ex | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/drowzee/k8s/deployment.ex b/lib/drowzee/k8s/deployment.ex index 9edacb1..cedda15 100644 --- a/lib/drowzee/k8s/deployment.ex +++ b/lib/drowzee/k8s/deployment.ex @@ -14,7 +14,7 @@ defmodule Drowzee.K8s.Deployment do """ def save_original_replicas(deployment) do current = get_in(deployment, ["spec", "replicas"]) || 0 - put_in(deployment, ["metadata", "annotations", "drowzee.chirpwireless.io/original-replicas"], Integer.to_string(current)) + put_in(deployment, ["metadata", "annotations", "drowzee.io/original-replicas"], Integer.to_string(current)) end @doc """ @@ -22,7 +22,7 @@ defmodule Drowzee.K8s.Deployment do """ def get_original_replicas(deployment) do annotations = get_in(deployment, ["metadata", "annotations"]) || %{} - case Map.get(annotations, "drowzee.chirpwireless.io/original-replicas") do + case Map.get(annotations, "drowzee.io/original-replicas") do nil -> 1 value -> case Integer.parse(value) do diff --git a/lib/drowzee/k8s/statefuleset.ex b/lib/drowzee/k8s/statefuleset.ex index d36a2d4..1b3de5c 100644 --- a/lib/drowzee/k8s/statefuleset.ex +++ b/lib/drowzee/k8s/statefuleset.ex @@ -14,7 +14,7 @@ defmodule Drowzee.K8s.StatefulSet do """ def save_original_replicas(statefulset) do current = get_in(statefulset, ["spec", "replicas"]) || 0 - put_in(statefulset, ["metadata", "annotations", "drowzee.chirpwireless.io/original-replicas"], Integer.to_string(current)) + put_in(statefulset, ["metadata", "annotations", "drowzee.io/original-replicas"], Integer.to_string(current)) end @doc """ @@ -22,7 +22,7 @@ defmodule Drowzee.K8s.StatefulSet do """ def get_original_replicas(statefulset) do annotations = get_in(statefulset, ["metadata", "annotations"]) || %{} - case Map.get(annotations, "drowzee.chirpwireless.io/original-replicas") do + case Map.get(annotations, "drowzee.io/original-replicas") do nil -> 1 value -> case Integer.parse(value) do From 78b9a63076201a4cafaec7fb500032e1777092de Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 23:45:39 +0200 Subject: [PATCH 29/62] fix: Update save_original_replicas functions --- lib/drowzee/k8s/deployment.ex | 15 ++++++++++++++- lib/drowzee/k8s/statefuleset.ex | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/lib/drowzee/k8s/deployment.ex b/lib/drowzee/k8s/deployment.ex index cedda15..e7f71e5 100644 --- a/lib/drowzee/k8s/deployment.ex +++ b/lib/drowzee/k8s/deployment.ex @@ -13,8 +13,21 @@ defmodule Drowzee.K8s.Deployment do Always save (or update) the current replica count as an annotation on the deployment. """ def save_original_replicas(deployment) do + annotations = get_in(deployment, ["metadata", "annotations"]) || %{} current = get_in(deployment, ["spec", "replicas"]) || 0 - put_in(deployment, ["metadata", "annotations", "drowzee.io/original-replicas"], Integer.to_string(current)) + + # Only save if: + # - annotation is missing OR + # - annotation value != current AND current > 0 + # This way, if someone changed the replica count while awake, you update it before sleep + case Map.get(annotations, "drowzee.io/original-replicas") do + nil when current > 0 -> + put_in(deployment, ["metadata", "annotations", "drowzee.io/original-replicas"], Integer.to_string(current)) + value when value != Integer.to_string(current) and current > 0 -> + put_in(deployment, ["metadata", "annotations", "drowzee.io/original-replicas"], Integer.to_string(current)) + _ -> + deployment + end end @doc """ diff --git a/lib/drowzee/k8s/statefuleset.ex b/lib/drowzee/k8s/statefuleset.ex index 1b3de5c..c7d91da 100644 --- a/lib/drowzee/k8s/statefuleset.ex +++ b/lib/drowzee/k8s/statefuleset.ex @@ -13,8 +13,21 @@ defmodule Drowzee.K8s.StatefulSet do Always save (or update) the current replica count as an annotation on the statefulset. """ def save_original_replicas(statefulset) do + annotations = get_in(statefulset, ["metadata", "annotations"]) || %{} current = get_in(statefulset, ["spec", "replicas"]) || 0 - put_in(statefulset, ["metadata", "annotations", "drowzee.io/original-replicas"], Integer.to_string(current)) + + # Only save if: + # - annotation is missing OR + # - annotation value != current AND current > 0 + # This way, if someone changed the replica count while awake, you update it before sleep + case Map.get(annotations, "drowzee.io/original-replicas") do + nil when current > 0 -> + put_in(statefulset, ["metadata", "annotations", "drowzee.io/original-replicas"], Integer.to_string(current)) + value when value != Integer.to_string(current) and current > 0 -> + put_in(statefulset, ["metadata", "annotations", "drowzee.io/original-replicas"], Integer.to_string(current)) + _ -> + statefulset + end end @doc """ From e8e8bb82817c734a12ad12434779ddf57d1402a1 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Wed, 7 May 2025 23:52:02 +0200 Subject: [PATCH 30/62] fix: Fix build --- lib/drowzee/k8s/deployment.ex | 7 ++++--- lib/drowzee/k8s/statefuleset.ex | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/drowzee/k8s/deployment.ex b/lib/drowzee/k8s/deployment.ex index e7f71e5..a37a153 100644 --- a/lib/drowzee/k8s/deployment.ex +++ b/lib/drowzee/k8s/deployment.ex @@ -15,6 +15,7 @@ defmodule Drowzee.K8s.Deployment do def save_original_replicas(deployment) do annotations = get_in(deployment, ["metadata", "annotations"]) || %{} current = get_in(deployment, ["spec", "replicas"]) || 0 + current_str = Integer.to_string(current) # Only save if: # - annotation is missing OR @@ -22,9 +23,9 @@ defmodule Drowzee.K8s.Deployment do # This way, if someone changed the replica count while awake, you update it before sleep case Map.get(annotations, "drowzee.io/original-replicas") do nil when current > 0 -> - put_in(deployment, ["metadata", "annotations", "drowzee.io/original-replicas"], Integer.to_string(current)) - value when value != Integer.to_string(current) and current > 0 -> - put_in(deployment, ["metadata", "annotations", "drowzee.io/original-replicas"], Integer.to_string(current)) + put_in(deployment, ["metadata", "annotations", "drowzee.io/original-replicas"], current_str) + value when value != current_str and current > 0 -> + put_in(deployment, ["metadata", "annotations", "drowzee.io/original-replicas"], current_str) _ -> deployment end diff --git a/lib/drowzee/k8s/statefuleset.ex b/lib/drowzee/k8s/statefuleset.ex index c7d91da..cbaf15b 100644 --- a/lib/drowzee/k8s/statefuleset.ex +++ b/lib/drowzee/k8s/statefuleset.ex @@ -15,6 +15,7 @@ defmodule Drowzee.K8s.StatefulSet do def save_original_replicas(statefulset) do annotations = get_in(statefulset, ["metadata", "annotations"]) || %{} current = get_in(statefulset, ["spec", "replicas"]) || 0 + current_str = Integer.to_string(current) # Only save if: # - annotation is missing OR @@ -22,9 +23,9 @@ defmodule Drowzee.K8s.StatefulSet do # This way, if someone changed the replica count while awake, you update it before sleep case Map.get(annotations, "drowzee.io/original-replicas") do nil when current > 0 -> - put_in(statefulset, ["metadata", "annotations", "drowzee.io/original-replicas"], Integer.to_string(current)) - value when value != Integer.to_string(current) and current > 0 -> - put_in(statefulset, ["metadata", "annotations", "drowzee.io/original-replicas"], Integer.to_string(current)) + put_in(statefulset, ["metadata", "annotations", "drowzee.io/original-replicas"], current_str) + value when value != current_str and current > 0 -> + put_in(statefulset, ["metadata", "annotations", "drowzee.io/original-replicas"], current_str) _ -> statefulset end From 66fa18abd7f28d4e581e2be09edf39d5004ded76 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 09:35:35 +0200 Subject: [PATCH 31/62] feat: Add enable/disable schedule button --- chart/values.yaml | 2 +- .../controllers/sleep_schedule_controller.ex | 5 ++ lib/drowzee/v1beta1/sleep_schedule.ex | 5 ++ lib/drowzee_web/live/home_live/index.ex | 16 ++++ .../live/home_live/index.html.heex | 78 ++++++++++++------- 5 files changed, 76 insertions(+), 30 deletions(-) diff --git a/chart/values.yaml b/chart/values.yaml index 44defa0..6fb175d 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -27,7 +27,7 @@ replicaCount: 1 # This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/ image: - repository: "ghcr.io/chirpwireless/drowzee" + repository: "colharris/drowzee" # This sets the pull policy for images. pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. diff --git a/lib/drowzee/controllers/sleep_schedule_controller.ex b/lib/drowzee/controllers/sleep_schedule_controller.ex index 5e4ed88..5a80982 100644 --- a/lib/drowzee/controllers/sleep_schedule_controller.ex +++ b/lib/drowzee/controllers/sleep_schedule_controller.ex @@ -11,6 +11,11 @@ defmodule Drowzee.Controller.SleepScheduleController do step Bonny.Pluggable.SkipObservedGenerations step :handle_event + def handle_event(%Bonny.Axn{resource: %{"spec" => %{"enabled" => false}}} = axn, _opts) do + Logger.info("Schedule #{axn.resource["metadata"]["name"]} is disabled. Skipping.") + axn + end + def handle_event(%Bonny.Axn{action: action} = axn, _opts) when action in [:add, :modify, :reconcile] do Logger.metadata( diff --git a/lib/drowzee/v1beta1/sleep_schedule.ex b/lib/drowzee/v1beta1/sleep_schedule.ex index 03c9d68..10237a5 100644 --- a/lib/drowzee/v1beta1/sleep_schedule.ex +++ b/lib/drowzee/v1beta1/sleep_schedule.ex @@ -51,6 +51,11 @@ defmodule Drowzee.API.V1Beta1.SleepSchedule do spec: %{ type: :object, properties: %{ + enabled: %{ + type: :boolean, + description: "Whether this schedule is enabled.", + default: true + }, deployments: %{ description: "The deployments that will be slept/woken.", type: :array, diff --git a/lib/drowzee_web/live/home_live/index.ex b/lib/drowzee_web/live/home_live/index.ex index 08948d2..411a49d 100644 --- a/lib/drowzee_web/live/home_live/index.ex +++ b/lib/drowzee_web/live/home_live/index.ex @@ -99,6 +99,22 @@ defmodule DrowzeeWeb.HomeLive.Index do {:noreply, socket} end + @impl true + def handle_event("toggle_enabled", %{"name" => name, "namespace" => namespace}, socket) do + sleep_schedule = Drowzee.K8s.get_sleep_schedule!(name, namespace) + enabled = sleep_schedule["spec"]["enabled"] + new_enabled = not enabled + updated_sleep_schedule = + put_in(sleep_schedule, ["spec", "enabled"], new_enabled) + |> Drowzee.K8s.update_sleep_schedule() + |> case do + {:ok, s} -> s + {:error, _} -> sleep_schedule + end + socket = replace_sleep_schedule(socket, updated_sleep_schedule) + {:noreply, socket} + end + @impl true def handle_event("sleep_all_schedules", %{"namespace" => namespace}, socket) when is_binary(namespace) do sleep_schedules = Drowzee.K8s.sleep_schedules(namespace) diff --git a/lib/drowzee_web/live/home_live/index.html.heex b/lib/drowzee_web/live/home_live/index.html.heex index c8b896a..87e0306 100644 --- a/lib/drowzee_web/live/home_live/index.html.heex +++ b/lib/drowzee_web/live/home_live/index.html.heex @@ -90,37 +90,57 @@ <% end %> - <%= if get_condition(sleep_schedule, "Sleeping")["status"] == "True" do %> - <%= if get_condition(sleep_schedule, "Transitioning")["status"] == "True" do %> - - <% else %> - <.link - phx-click="wake_schedule" - phx-value-name={sleep_schedule["metadata"]["name"]} - phx-value-namespace={sleep_schedule["metadata"]["namespace"]} - class="px-4 py-2 text-sm rounded-lg bg-green-500 text-white hover:bg-green-600 disabled:bg-green-300 disabled:cursor-not-allowed" - > - ☕️ Wake Up - - <% end %> - <% else %> - <%= if get_condition(sleep_schedule, "Transitioning")["status"] == "True" do %> - +
            + <%= if get_condition(sleep_schedule, "Sleeping")["status"] == "True" do %> + <%= if get_condition(sleep_schedule, "Transitioning")["status"] == "True" do %> + + <% else %> + <.link + phx-click="wake_schedule" + phx-value-name={sleep_schedule["metadata"]["name"]} + phx-value-namespace={sleep_schedule["metadata"]["namespace"]} + class="px-4 py-2 text-sm rounded-lg bg-green-500 text-white hover:bg-green-600 disabled:bg-green-300 disabled:cursor-not-allowed" + > + ☕️ Wake Up + + <% end %> <% else %> - <.link - phx-click="sleep_schedule" - phx-value-name={sleep_schedule["metadata"]["name"]} - phx-value-namespace={sleep_schedule["metadata"]["namespace"]} - class="px-4 py-2 text-sm rounded-lg bg-blue-500 text-white hover:bg-blue-600 disabled:bg-blue-300 disabled:cursor-not-allowed" - > - 💤 Sleep - + <%= if get_condition(sleep_schedule, "Transitioning")["status"] == "True" do %> + + <% else %> + <.link + phx-click="sleep_schedule" + phx-value-name={sleep_schedule["metadata"]["name"]} + phx-value-namespace={sleep_schedule["metadata"]["namespace"]} + class="px-4 py-2 text-sm rounded-lg bg-blue-500 text-white hover:bg-blue-600 disabled:bg-blue-300 disabled:cursor-not-allowed" + > + 💤 Sleep + + <% end %> <% end %> - <% end %> + <.link + phx-click="toggle_enabled" + phx-value-name={sleep_schedule["metadata"]["name"]} + phx-value-namespace={sleep_schedule["metadata"]["namespace"]} + class={ + if sleep_schedule["spec"]["enabled"] == false do + "px-4 py-2 text-sm rounded-lg bg-gray-400 text-white hover:bg-gray-500" + else + "px-4 py-2 text-sm rounded-lg bg-yellow-400 text-white hover:bg-yellow-500" + end + } + > + <%= if sleep_schedule["spec"]["enabled"] == false do %> + ▶️ Enable + <% else %> + ⏸️ Disable + <% end %> + +
      From 5769250ec72fb8336cf3843120c475c2c57a3de9 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 10:10:27 +0200 Subject: [PATCH 32/62] feat: Update dependencies --- mix.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/mix.lock b/mix.lock index db58d77..3735a64 100644 --- a/mix.lock +++ b/mix.lock @@ -1,7 +1,7 @@ %{ - "bandit": {:hex, :bandit, "1.6.7", "42f30e37a1c89a2a12943c5dca76f731a2313e8a2e21c1a95dc8241893e922d1", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "551ba8ff5e4fc908cbeb8c9f0697775fb6813a96d9de5f7fe02e34e76fd7d184"}, + "bandit": {:hex, :bandit, "1.6.11", "2fbadd60c95310eefb4ba7f1e58810aa8956e18c664a3b2029d57edb7d28d410", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "543f3f06b4721619a1220bed743aa77bf7ecc9c093ba9fab9229ff6b99eacc65"}, "bonny": {:hex, :bonny, "1.4.0", "318e8fd23129e7c1b1e2d949fa29d75cff348d0cc3330f1dc902f24f0a7c429c", [:mix], [{:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:k8s, "~> 2.0", [hex: :k8s, repo: "hexpm", optional: false]}, {:owl, "~> 0.8.0", [hex: :owl, repo: "hexpm", optional: false]}, {:pluggable, "~> 1.0", [hex: :pluggable, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "91b3ba068c3724ab9abc95e053c41edf0f9990d1cd841b91cc494521fda6a756"}, - "castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"}, + "castore": {:hex, :castore, "1.0.12", "053f0e32700cbec356280c0e835df425a3be4bc1e0627b714330ad9d0f05497f", [:mix], [], "hexpm", "3dca286b2186055ba0c9449b4e95b97bf1b57b47c1f2644555879e659960c224"}, "certifi": {:hex, :certifi, "2.14.0", "ed3bef654e69cde5e6c022df8070a579a79e8ba2368a00acf3d75b82d9aceeed", [:rebar3], [], "hexpm", "ea59d87ef89da429b8e905264fdec3419f84f2215bb3d81e07a18aac919026c3"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"}, @@ -9,12 +9,12 @@ "expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"}, "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, - "floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"}, + "floki": {:hex, :floki, "0.37.1", "d7aaee758c8a5b4a7495799a4260754fec5530d95b9c383c03b27359dea117cf", [:mix], [], "hexpm", "673d040cb594d31318d514590246b6dd587ed341d3b67e17c1c0eb8ce7ca6f04"}, "gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, - "hackney": {:hex, :hackney, "1.22.0", "4efc68df70322d4d2e3d2744e9bd191a39a0cb8d08c35379a08d9fb0f040d595", [:rebar3], [{:certifi, "~> 2.14.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "628569e451820950382be3d3e6481d7c59997e606c7823bddb4ce5d10812dfcb"}, + "hackney": {:hex, :hackney, "1.23.0", "55cc09077112bcb4a69e54be46ed9bc55537763a96cd4a80a221663a7eafd767", [:rebar3], [{:certifi, "~> 2.14.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "6cd1c04cd15c81e5a493f167b226a15f0938a84fc8f0736ebe4ddcab65c0b44e"}, "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized", depth: 1]}, - "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, + "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "igniter": {:hex, :igniter, "0.5.15", "422c7f25340c8846eaad6c0f63b72dfd74019829130fca5bf75ad5f3716e97ec", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "dcea787ddb5519a6bdb1799b373fabe553e5ffcefc17c85688e7bbbceee25a97"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, @@ -29,15 +29,15 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.8.0", "0ef925cb784311093d4e3734822960cbdbdb13b095d748bb5bc82abcd5b56732", [:mix], [], "hexpm", "0a5586ceb1a12f4bbda90e330c20e6ea034552335d09466c10e4218c98977529"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, - "phoenix": {:hex, :phoenix, "1.7.19", "36617efe5afbd821099a8b994ff4618a340a5bfb25531a1802c4d4c634017a57", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "ba4dc14458278773f905f8ae6c2ec743d52c3a35b6b353733f64f02dfe096cd6"}, - "phoenix_html": {:hex, :phoenix_html, "4.2.0", "83a4d351b66f472ebcce242e4ae48af1b781866f00ef0eb34c15030d4e2069ac", [:mix], [], "hexpm", "9713b3f238d07043583a94296cc4bbdceacd3b3a6c74667f4df13971e7866ec8"}, - "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.6", "7b1f0327f54c9eb69845fd09a77accf922f488c549a7e7b8618775eb603a62c7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1681ab813ec26ca6915beb3414aa138f298e17721dc6a2bde9e6eb8a62360ff6"}, - "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.4", "327491b033e79db2f887b065c5a2993228449091883d74cfa1baa12f8c98d5eb", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a9865316ddf8d78f382d63af278d20436b52d262b60239956817a61279514366"}, + "phoenix": {:hex, :phoenix, "1.7.21", "14ca4f1071a5f65121217d6b57ac5712d1857e40a0833aff7a691b7870fc9a3b", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "336dce4f86cba56fed312a7d280bf2282c720abb6074bdb1b61ec8095bdd0bc9"}, + "phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"}, + "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.6.0", "2791fac0e2776b640192308cc90c0dbcf67843ad51387ed4ecae2038263d708d", [:mix], [{:file_system, "~> 0.2.10 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b3a1fa036d7eb2f956774eda7a7638cf5123f8f2175aca6d6420a7f95e598e1c"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.11", "76f6416cb31e2e49601c62b79edc9e6fbd0706368bba9f49c4bd13934b785a28", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "522b8c164d1a0009d30fd3364538d17684cb6f8e6a6931f511f82d891c634cdd"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, - "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, - "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, + "plug": {:hex, :plug, "1.17.0", "a0832e7af4ae0f4819e0c08dd2e7482364937aea6a8a997a679f2cbb7e026b2e", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6692046652a69a00a5a21d0b7e11fcf401064839d59d6b8787f23af55b1e6bc"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, "pluggable": {:hex, :pluggable, "1.1.0", "7eba3bc70c0caf4d9056c63c882df8862f7534f0145da7ab3a47ca73e4adb1e4", [:mix], [], "hexpm", "d12eb00ea47b21e92cd2700d6fbe3737f04b64e71b63aad1c0accde87c751637"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, "req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"}, @@ -46,14 +46,14 @@ "sourceror": {:hex, :sourceror, "1.9.0", "3bf5fe2d017aaabe3866d8a6da097dd7c331e0d2d54e59e21c2b066d47f1e08e", [:mix], [], "hexpm", "d20a9dd5efe162f0d75a307146faa2e17b823ea4f134f662358d70f0332fed82"}, "spitfire": {:hex, :spitfire, "0.2.0", "0de1f519a23f65bde40d316adad53c07a9563f25cc68915d639d8a509a0aad8a", [:mix], [], "hexpm", "743daaee2d81a0d8095431729f478ce49b47ea8943c7d770de86704975cb7775"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "tailwind": {:hex, :tailwind, "0.2.4", "5706ec47182d4e7045901302bf3a333e80f3d1af65c442ba9a9eed152fb26c2e", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "c6e4a82b8727bab593700c998a4d98cf3d8025678bfde059aed71d0000c3e463"}, + "tailwind": {:hex, :tailwind, "0.3.1", "a89d2835c580748c7a975ad7dd3f2ea5e63216dc16d44f9df492fbd12c094bed", [:mix], [], "hexpm", "98a45febdf4a87bc26682e1171acdedd6317d0919953c353fcd1b4f9f4b676a2"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, - "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, + "telemetry_poller": {:hex, :telemetry_poller, "1.2.0", "ba82e333215aed9dd2096f93bd1d13ae89d249f82760fcada0850ba33bac154b", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7216e21a6c326eb9aa44328028c34e9fd348fb53667ca837be59d0aa2a0156e8"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, - "thousand_island": {:hex, :thousand_island, "1.3.10", "a9971ebab1dfb36e2710a86b37c3f54973fbc9470d892035334415521fb53328", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17ab1f1b13aadb1f4b4c8e5b59c06874d701119fed082884c9c6d38addad254f"}, + "thousand_island": {:hex, :thousand_island, "1.3.13", "d598c609172275f7b1648c9f6eddf900e42312b09bfc2f2020358f926ee00d39", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5a34bdf24ae2f965ddf7ba1a416f3111cfe7df50de8d66f6310e01fc2e80b02a"}, "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, - "tzdata": {:hex, :tzdata, "1.1.2", "45e5f1fcf8729525ec27c65e163be5b3d247ab1702581a94674e008413eef50b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cec7b286e608371602318c414f344941d5eb0375e14cfdab605cca2fe66cba8b"}, + "tzdata": {:hex, :tzdata, "1.1.3", "b1cef7bb6de1de90d4ddc25d33892b32830f907e7fc2fccd1e7e22778ab7dfbc", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "d4ca85575a064d29d4e94253ee95912edfb165938743dbf002acdf0dcecb0c28"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, From 29f1da7ffccbe4a00dc594731f0b9f314513a974 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 10:24:12 +0200 Subject: [PATCH 33/62] fix: Update enable/disable field --- chart/crds/sleepschedule.yaml | 4 ++++ lib/drowzee/controllers/sleep_schedule_controller.ex | 2 +- lib/drowzee/v1beta1/sleep_schedule.ex | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/chart/crds/sleepschedule.yaml b/chart/crds/sleepschedule.yaml index e04cde1..269af55 100644 --- a/chart/crds/sleepschedule.yaml +++ b/chart/crds/sleepschedule.yaml @@ -97,6 +97,10 @@ spec: description: "The time that the deployment will wake up (format: HH:MMam/pm)" storage: true additionalPrinterColumns: + - name: Enabled + type: string + description: Enabled + jsonPath: .spec.enabled - name: SleepTime type: string description: Starts Sleeping diff --git a/lib/drowzee/controllers/sleep_schedule_controller.ex b/lib/drowzee/controllers/sleep_schedule_controller.ex index 5a80982..d8be4cb 100644 --- a/lib/drowzee/controllers/sleep_schedule_controller.ex +++ b/lib/drowzee/controllers/sleep_schedule_controller.ex @@ -11,7 +11,7 @@ defmodule Drowzee.Controller.SleepScheduleController do step Bonny.Pluggable.SkipObservedGenerations step :handle_event - def handle_event(%Bonny.Axn{resource: %{"spec" => %{"enabled" => false}}} = axn, _opts) do + def handle_event(%Bonny.Axn{resource: %{"spec" => spec}} = axn, _opts) when Map.get(spec, "enabled", true) == false do Logger.info("Schedule #{axn.resource["metadata"]["name"]} is disabled. Skipping.") axn end diff --git a/lib/drowzee/v1beta1/sleep_schedule.ex b/lib/drowzee/v1beta1/sleep_schedule.ex index 10237a5..49ae6cd 100644 --- a/lib/drowzee/v1beta1/sleep_schedule.ex +++ b/lib/drowzee/v1beta1/sleep_schedule.ex @@ -92,10 +92,13 @@ defmodule Drowzee.API.V1Beta1.SleepSchedule do } }, additionalPrinterColumns: [ + %{name: "Enabled", type: :string, description: "Enabled", jsonPath: ".spec.enabled"}, %{name: "SleepTime", type: :string, description: "Starts Sleeping", jsonPath: ".spec.sleepTime"}, %{name: "WakeTime", type: :string, description: "Wakes Up", jsonPath: ".spec.wakeTime"}, %{name: "Timezone", type: :string, description: "Timezone", jsonPath: ".spec.timezone"}, %{name: "Deployments", type: :string, description: "Deployments", jsonPath: ".spec.deployments[*].name"}, + %{name: "Statefulsets", type: :string, description: "Statefulsets", jsonPath: ".spec.statefulsets[*].name"}, + %{name: "Cronjobs", type: :string, description: "CronJobs", jsonPath: ".spec.cronjobs[*].name"}, %{name: "Sleeping?", type: :string, description: "Current Status", jsonPath: ".status.conditions[?(@.type == \"Sleeping\")].status"}, %{name: "Transitioning?", type: :string, description: "Status Change In Progress", jsonPath: ".status.conditions[?(@.type == \"Transitioning\")].status"}, %{name: "ManualOverride?", type: :string, description: "Status overridden by user", jsonPath: ".status.conditions[?(@.type == \"ManualOverride\")].status"} From 03e2d02894ef4c3b4c6e7a32de7c96dc71e3874e Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 10:26:25 +0200 Subject: [PATCH 34/62] fix: Add update_sleep_schedule function --- lib/drowzee/k8s/sleep_schedule.ex | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/drowzee/k8s/sleep_schedule.ex b/lib/drowzee/k8s/sleep_schedule.ex index 7b65f85..811a63d 100644 --- a/lib/drowzee/k8s/sleep_schedule.ex +++ b/lib/drowzee/k8s/sleep_schedule.ex @@ -209,6 +209,12 @@ defmodule Drowzee.K8s.SleepSchedule do end end + def update_sleep_schedule(sleep_schedule) do + require Logger + Logger.info("Updating SleepSchedule #{sleep_schedule["metadata"]["name"]} in namespace #{sleep_schedule["metadata"]["namespace"]}") + K8s.Client.run(Drowzee.K8s.conn(), K8s.Client.update(sleep_schedule)) + end + def update_heartbeat(sleep_schedule) do Logger.debug("Update heartbeat...") heartbeat = get_condition(sleep_schedule, "Heartbeat") || %{ "status" => "False" } From 835617bd71946a865d73b989c392b5aeefce05db Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 10:29:05 +0200 Subject: [PATCH 35/62] fix: Update event handle --- lib/drowzee/controllers/sleep_schedule_controller.ex | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/drowzee/controllers/sleep_schedule_controller.ex b/lib/drowzee/controllers/sleep_schedule_controller.ex index d8be4cb..a34c489 100644 --- a/lib/drowzee/controllers/sleep_schedule_controller.ex +++ b/lib/drowzee/controllers/sleep_schedule_controller.ex @@ -11,9 +11,13 @@ defmodule Drowzee.Controller.SleepScheduleController do step Bonny.Pluggable.SkipObservedGenerations step :handle_event - def handle_event(%Bonny.Axn{resource: %{"spec" => spec}} = axn, _opts) when Map.get(spec, "enabled", true) == false do - Logger.info("Schedule #{axn.resource["metadata"]["name"]} is disabled. Skipping.") - axn + def handle_event(%Bonny.Axn{resource: %{"spec" => spec}} = axn, _opts) do + if Map.get(spec, "enabled", true) == false do + Logger.info("Schedule #{axn.resource["metadata"]["name"]} is disabled. Skipping.") + axn + else + handle_event_enabled(axn, _opts) + end end def handle_event(%Bonny.Axn{action: action} = axn, _opts) From 9397ab1788addfd176a1f43a0ff97316031f7048 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 10:33:41 +0200 Subject: [PATCH 36/62] fix: Fix naming --- .github/workflows/pipeline-build.yml | 2 +- lib/drowzee/controllers/sleep_schedule_controller.ex | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pipeline-build.yml b/.github/workflows/pipeline-build.yml index 6c051f9..08da0a0 100644 --- a/.github/workflows/pipeline-build.yml +++ b/.github/workflows/pipeline-build.yml @@ -52,7 +52,7 @@ jobs: build-helm-chart: runs-on: ubuntu-latest - needs: [generate-version] + needs: [generate-version, build-docker-image] permissions: contents: write steps: diff --git a/lib/drowzee/controllers/sleep_schedule_controller.ex b/lib/drowzee/controllers/sleep_schedule_controller.ex index a34c489..5cb6eda 100644 --- a/lib/drowzee/controllers/sleep_schedule_controller.ex +++ b/lib/drowzee/controllers/sleep_schedule_controller.ex @@ -20,7 +20,7 @@ defmodule Drowzee.Controller.SleepScheduleController do end end - def handle_event(%Bonny.Axn{action: action} = axn, _opts) + def handle_event_enabled(%Bonny.Axn{action: action} = axn, _opts) when action in [:add, :modify, :reconcile] do Logger.metadata( schedule: axn.resource["metadata"]["name"], @@ -35,7 +35,7 @@ defmodule Drowzee.Controller.SleepScheduleController do end # delete the resource - def handle_event(%Bonny.Axn{action: :delete} = axn, _opts) do + def handle_event_enabled(%Bonny.Axn{action: :delete} = axn, _opts) do Logger.warning("Delete Action - Not yet implemented!") # TODO: # - Make sure deployments are awake From a7fffa59bce8e79b6db9c1fd5643072a83f758c2 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 10:44:01 +0200 Subject: [PATCH 37/62] feat: Update versioning --- .github/workflows/pipeline-build.yml | 15 ++++++++++++++- chart/Chart.yaml | 4 ++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pipeline-build.yml b/.github/workflows/pipeline-build.yml index 08da0a0..3dc8c23 100644 --- a/.github/workflows/pipeline-build.yml +++ b/.github/workflows/pipeline-build.yml @@ -58,9 +58,22 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + - name: Compute Chart Version + id: chart_version + run: | + BASE_VERSION=$(grep '^version:' chart/Chart.yaml | awk '{print $2}') + if [[ "${GITHUB_REF##*/}" == "main" ]]; then + VERSION="$BASE_VERSION" + else + PATCH=$(echo $BASE_VERSION | awk -F. '{print $3}') + PATCH=$((PATCH + 1)) + PREFIX=$(echo $BASE_VERSION | awk -F. '{print $1"."$2}') + VERSION="${PREFIX}.${PATCH}-${{ needs.generate-version.outputs.service-version }}" + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT - name: Update Chart Versions run: | - sed -i "s/^version: .*/version: ${{ needs.generate-version.outputs.service-version }}/" chart/Chart.yaml + sed -i "s/^version: .*/version: ${{ steps.chart_version.outputs.version }}/" chart/Chart.yaml sed -i "s/^appVersion: .*/appVersion: \"${{ needs.generate-version.outputs.service-version }}\"/" chart/Chart.yaml - name: Retrieve existing charts run: | diff --git a/chart/Chart.yaml b/chart/Chart.yaml index 3360b7c..a0b64db 100644 --- a/chart/Chart.yaml +++ b/chart/Chart.yaml @@ -15,10 +15,10 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.8 +version: 0.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.1.8" +appVersion: "0.1.0" From 91bab4940e5e3e6423f906b7f6dbfd7d42992ca2 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 10:48:38 +0200 Subject: [PATCH 38/62] feat: Update versioning --- .github/workflows/pipeline-build.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/pipeline-build.yml b/.github/workflows/pipeline-build.yml index 3dc8c23..ad52e35 100644 --- a/.github/workflows/pipeline-build.yml +++ b/.github/workflows/pipeline-build.yml @@ -65,10 +65,7 @@ jobs: if [[ "${GITHUB_REF##*/}" == "main" ]]; then VERSION="$BASE_VERSION" else - PATCH=$(echo $BASE_VERSION | awk -F. '{print $3}') - PATCH=$((PATCH + 1)) - PREFIX=$(echo $BASE_VERSION | awk -F. '{print $1"."$2}') - VERSION="${PREFIX}.${PATCH}-${{ needs.generate-version.outputs.service-version }}" + VERSION="${BASE_VERSION}-${{ needs.generate-version.outputs.service-version }}" fi echo "version=$VERSION" >> $GITHUB_OUTPUT - name: Update Chart Versions From f8ac6ac21764385c95b39f4f435fda9b389fdb02 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 11:00:32 +0200 Subject: [PATCH 39/62] fix: Enabled/Disabled feature --- lib/drowzee_web/live/home_live/index.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/drowzee_web/live/home_live/index.ex b/lib/drowzee_web/live/home_live/index.ex index 411a49d..8559e53 100644 --- a/lib/drowzee_web/live/home_live/index.ex +++ b/lib/drowzee_web/live/home_live/index.ex @@ -102,8 +102,7 @@ defmodule DrowzeeWeb.HomeLive.Index do @impl true def handle_event("toggle_enabled", %{"name" => name, "namespace" => namespace}, socket) do sleep_schedule = Drowzee.K8s.get_sleep_schedule!(name, namespace) - enabled = sleep_schedule["spec"]["enabled"] - new_enabled = not enabled + new_enabled = not Map.get(sleep_schedule["spec"], "enabled", true) updated_sleep_schedule = put_in(sleep_schedule, ["spec", "enabled"], new_enabled) |> Drowzee.K8s.update_sleep_schedule() From ba99f2842ffdbf71d4fd5d5ee6408fa5100869f1 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 11:05:01 +0200 Subject: [PATCH 40/62] feat: Update versioning --- .github/workflows/pipeline-build.yml | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/pipeline-build.yml b/.github/workflows/pipeline-build.yml index ad52e35..a7061cc 100644 --- a/.github/workflows/pipeline-build.yml +++ b/.github/workflows/pipeline-build.yml @@ -19,13 +19,25 @@ jobs: runs-on: ubuntu-latest outputs: service-version: ${{ steps.version.outputs.new_version }} + chart-version: ${{ steps.chart_version.outputs.new_version }} steps: - uses: actions/checkout@v4 - id: version uses: chirpwireless/reusable-github-actions/.github/actions/calendar-version@main + - name: Chart Version + id: chart_version + run: | + BASE_VERSION=$(grep '^version:' chart/Chart.yaml | awk '{print $2}') + if [[ "${GITHUB_REF##*/}" == "main" ]]; then + VERSION="$BASE_VERSION" + else + VERSION="${BASE_VERSION}-${{steps.version.outputs.new_version}}" + fi + echo "new_version=$VERSION" >> $GITHUB_OUTPUT - name: "Create a summary" run: | - echo "Building a new revision ${{steps.version.outputs.new_version}}" > $GITHUB_STEP_SUMMARY + echo "Building a new revision ${{steps.version.outputs.new_version}}\n" > $GITHUB_STEP_SUMMARY + echo "Building a new chart revision ${{steps.chart_version.outputs.new_version}}" >> $GITHUB_STEP_SUMMARY build-docker-image: runs-on: ubuntu-latest @@ -58,19 +70,9 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - - name: Compute Chart Version - id: chart_version - run: | - BASE_VERSION=$(grep '^version:' chart/Chart.yaml | awk '{print $2}') - if [[ "${GITHUB_REF##*/}" == "main" ]]; then - VERSION="$BASE_VERSION" - else - VERSION="${BASE_VERSION}-${{ needs.generate-version.outputs.service-version }}" - fi - echo "version=$VERSION" >> $GITHUB_OUTPUT - name: Update Chart Versions run: | - sed -i "s/^version: .*/version: ${{ steps.chart_version.outputs.version }}/" chart/Chart.yaml + sed -i "s/^version: .*/version: ${{ needs.generate-version.outputs.chart-version }}/" chart/Chart.yaml sed -i "s/^appVersion: .*/appVersion: \"${{ needs.generate-version.outputs.service-version }}\"/" chart/Chart.yaml - name: Retrieve existing charts run: | From d9c3ea944130d52b508d5966a3d7cbcc03cccbc8 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 11:12:10 +0200 Subject: [PATCH 41/62] fix: Update enabled/disabled --- .github/workflows/pipeline-build.yml | 2 +- lib/drowzee/k8s.ex | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pipeline-build.yml b/.github/workflows/pipeline-build.yml index a7061cc..82bd25f 100644 --- a/.github/workflows/pipeline-build.yml +++ b/.github/workflows/pipeline-build.yml @@ -36,7 +36,7 @@ jobs: echo "new_version=$VERSION" >> $GITHUB_OUTPUT - name: "Create a summary" run: | - echo "Building a new revision ${{steps.version.outputs.new_version}}\n" > $GITHUB_STEP_SUMMARY + echo "Building a new revision ${{steps.version.outputs.new_version}}" >> $GITHUB_STEP_SUMMARY echo "Building a new chart revision ${{steps.chart_version.outputs.new_version}}" >> $GITHUB_STEP_SUMMARY build-docker-image: diff --git a/lib/drowzee/k8s.ex b/lib/drowzee/k8s.ex index 29131ad..f2f05f4 100644 --- a/lib/drowzee/k8s.ex +++ b/lib/drowzee/k8s.ex @@ -109,4 +109,8 @@ defmodule Drowzee.K8s do |> K8s.Client.put_conn(conn()) |> K8s.Client.run() end + + def update_sleep_schedule(sleep_schedule) do + Drowzee.K8s.SleepSchedule.update_sleep_schedule(sleep_schedule) + end end From 32e78a197595a278a77a426c42f1e9c129bafdc9 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 11:37:35 +0200 Subject: [PATCH 42/62] feat: Add logs --- lib/drowzee/k8s/sleep_schedule.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/drowzee/k8s/sleep_schedule.ex b/lib/drowzee/k8s/sleep_schedule.ex index 811a63d..5d76266 100644 --- a/lib/drowzee/k8s/sleep_schedule.ex +++ b/lib/drowzee/k8s/sleep_schedule.ex @@ -212,7 +212,9 @@ defmodule Drowzee.K8s.SleepSchedule do def update_sleep_schedule(sleep_schedule) do require Logger Logger.info("Updating SleepSchedule #{sleep_schedule["metadata"]["name"]} in namespace #{sleep_schedule["metadata"]["namespace"]}") - K8s.Client.run(Drowzee.K8s.conn(), K8s.Client.update(sleep_schedule)) + result = K8s.Client.run(Drowzee.K8s.conn(), K8s.Client.update(sleep_schedule)) + Logger.debug("Update result: #{inspect(result)}") + result end def update_heartbeat(sleep_schedule) do From dbbd9b7353ddcacf10c4c5258ad8f95722a81a99 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 11:47:52 +0200 Subject: [PATCH 43/62] fix: Update crd --- chart/crds/sleepschedule.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/chart/crds/sleepschedule.yaml b/chart/crds/sleepschedule.yaml index 269af55..ea6d73b 100644 --- a/chart/crds/sleepschedule.yaml +++ b/chart/crds/sleepschedule.yaml @@ -53,6 +53,10 @@ spec: - sleepTime - timezone properties: + enabled: + type: boolean + description: Whether the schedule is enabled + default: true deployments: type: array description: The deployments that will be slept/woken. @@ -98,7 +102,7 @@ spec: storage: true additionalPrinterColumns: - name: Enabled - type: string + type: boolean description: Enabled jsonPath: .spec.enabled - name: SleepTime From c98e22ec1a4d4426c9f5a1839d51fbe8a092b343 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 11:54:28 +0200 Subject: [PATCH 44/62] feat: Update some actions flow --- .github/CODEOWNERS | 1 - .github/dependabot.yml | 29 ++++++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4189f5a..e626e6f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,3 @@ # CODEOWNERS file allows us to set owners for specific files and directories # https://docs.github.com/en/enterprise-cloud@latest/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners -* @chirpwireless/infra diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fde5816..11416cc 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,10 +1,33 @@ version: 2 updates: - - package-ecosystem: terraform + # Maintain dependencies of docker images + - package-ecosystem: "docker" directory: "/" schedule: - interval: weekly + interval: "daily" + + # Maintain dependencies of GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + commit-message: + prefix: "ci:" + schedule: + interval: "daily" + labels: + - "Type: Dependencies" + - "Component: CI/CD" + + # Maintain dependencies of elixir applications + - package-ecosystem: "mix" + directory: "/" + commit-message: + prefix: "build:" + schedule: + interval: "daily" + labels: + - "Type: Dependencies" + - "Implementation: Elixir" groups: - all: + elixir-deps: patterns: - "*" From ec4ab43c31b7c4f2fa200b261e016ff2834e962c Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 12:31:06 +0200 Subject: [PATCH 45/62] fix: Small fixes --- .../controllers/sleep_schedule_controller.ex | 53 ++++++++++--------- lib/drowzee/transition_monitor.ex | 2 +- lib/drowzee/v1beta1/sleep_schedule.ex | 2 +- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/lib/drowzee/controllers/sleep_schedule_controller.ex b/lib/drowzee/controllers/sleep_schedule_controller.ex index 5cb6eda..2ae024b 100644 --- a/lib/drowzee/controllers/sleep_schedule_controller.ex +++ b/lib/drowzee/controllers/sleep_schedule_controller.ex @@ -12,8 +12,13 @@ defmodule Drowzee.Controller.SleepScheduleController do step :handle_event def handle_event(%Bonny.Axn{resource: %{"spec" => spec}} = axn, _opts) do + Logger.metadata( + schedule: axn.resource["metadata"]["name"], + namespace: axn.resource["metadata"]["namespace"] + ) + if Map.get(spec, "enabled", true) == false do - Logger.info("Schedule #{axn.resource["metadata"]["name"]} is disabled. Skipping.") + Logger.info("Schedule is disabled. Skipping...") axn else handle_event_enabled(axn, _opts) @@ -38,7 +43,7 @@ defmodule Drowzee.Controller.SleepScheduleController do def handle_event_enabled(%Bonny.Axn{action: :delete} = axn, _opts) do Logger.warning("Delete Action - Not yet implemented!") # TODO: - # - Make sure deployments are awake + # - Make sure applications are awake axn end @@ -66,7 +71,7 @@ defmodule Drowzee.Controller.SleepScheduleController do # Log if wake_time is not defined (resources will remain down indefinitely) if is_nil(wake_time) or wake_time == "" do - Logger.info("Wake time is not defined for #{resource["metadata"]["name"]} in namespace #{resource["metadata"]["namespace"]}. Resources will remain scaled down/suspended indefinitely.") + Logger.info("Wake time is not defined. Resources will remain scaled down/suspended indefinitely.") end case Drowzee.SleepChecker.naptime?(sleep_time, wake_time, timezone) do @@ -125,7 +130,7 @@ defmodule Drowzee.Controller.SleepScheduleController do axn |> update_hosts() |> set_condition("Transitioning", true, "Sleeping", "Going to sleep") - |> scale_down_deployments() + |> scale_down_applications() |> start_transition_monitor() end @@ -133,7 +138,7 @@ defmodule Drowzee.Controller.SleepScheduleController do Logger.info("Initiating wake up") axn |> set_condition("Transitioning", true, "WakingUp", "Waking up") - |> scale_up_deployments() + |> scale_up_applications() |> start_transition_monitor() end @@ -142,14 +147,14 @@ defmodule Drowzee.Controller.SleepScheduleController do axn end - defp scale_down_deployments(axn) do + defp scale_down_applications(axn) do SleepSchedule.scale_down_deployments(axn.resource) SleepSchedule.scale_down_statefulsets(axn.resource) SleepSchedule.suspend_cronjobs(axn.resource) axn end - defp scale_up_deployments(axn) do + defp scale_up_applications(axn) do SleepSchedule.scale_up_deployments(axn.resource) SleepSchedule.scale_up_statefulsets(axn.resource) SleepSchedule.resume_cronjobs(axn.resource) @@ -174,45 +179,45 @@ defmodule Drowzee.Controller.SleepScheduleController do defp check_sleep_transition(axn, opts) do Logger.info("Checking sleep transition...") - case check_deployment_status(axn, &deployment_status_asleep?/1) do + case check_application_status(axn, &application_status_asleep?/1) do {:ok, true} -> - Logger.debug("All deployments are asleep") + Logger.debug("All applications are asleep") axn |> put_ingress_to_sleep() |> complete_sleep_transition(opts) {:ok, false} -> - Logger.debug("Deployments have not yet scaled down...") - scale_down_deployments(axn) + Logger.debug("Applications have not yet scaled down...") + scale_down_applications(axn) {:error, [error | _]} -> - Logger.error("Failed to check deployment status: #{inspect(error)}") - set_condition(axn, "Error", true, "DeploymentNotFound", error.message) + Logger.error("Failed to check application status: #{inspect(error)}") + set_condition(axn, "Error", true, "ApplicationNotFound", error.message) {:error, error} -> - Logger.error("Failed to check deployment status: #{inspect(error)}") + Logger.error("Failed to check application status: #{inspect(error)}") axn end end defp check_wake_up_transition(axn, opts) do Logger.info("Checking wake up transition...") - case check_deployment_status(axn, &deployment_status_ready?/1) do + case check_application_status(axn, &application_status_ready?/1) do {:ok, true} -> - Logger.debug("All deployments are ready") + Logger.debug("All applications are ready") axn |> wake_up_ingress() |> complete_wake_up_transition(opts) {:ok, false} -> - Logger.debug("Deployments are not ready...") - scale_up_deployments(axn) + Logger.debug("Applications are not ready...") + scale_up_applications(axn) {:error, [error | _]} -> - Logger.error("Failed to check deployment status: #{inspect(error)}") - set_condition(axn, "Error", true, "DeploymentNotFound", error.message) + Logger.error("Failed to check application status: #{inspect(error)}") + set_condition(axn, "Error", true, "ApplicationNotFound", error.message) {:error, error} -> - Logger.error("Failed to check deployment status: #{inspect(error)}") + Logger.error("Failed to check applications status: #{inspect(error)}") axn end end - defp deployment_status_ready?(status) do + defp application_status_ready?(status) do replicas = Map.get(status, "replicas") ready_replicas = Map.get(status, "readyReplicas") suspended = Map.get(status, "suspended", true) @@ -227,7 +232,7 @@ defmodule Drowzee.Controller.SleepScheduleController do end end - defp deployment_status_asleep?(status) do + defp application_status_asleep?(status) do replicas = Map.get(status, "replicas", 0) ready = Map.get(status, "readyReplicas", 0) suspended = Map.get(status, "suspended", nil) @@ -241,7 +246,7 @@ defmodule Drowzee.Controller.SleepScheduleController do end end - defp check_deployment_status(axn, check_fn) do + defp check_application_status(axn, check_fn) do with {:ok, deployments} <- SleepSchedule.get_deployments(axn.resource), {:ok, statefulsets} <- SleepSchedule.get_statefulsets(axn.resource), {:ok, cronjobs} <- SleepSchedule.get_cronjobs(axn.resource) do diff --git a/lib/drowzee/transition_monitor.ex b/lib/drowzee/transition_monitor.ex index 751e15b..1db000a 100644 --- a/lib/drowzee/transition_monitor.ex +++ b/lib/drowzee/transition_monitor.ex @@ -14,7 +14,7 @@ defmodule Drowzee.TransitionMonitor do Logger.info("Monitoring transition...") task_name = "monitor_transition_#{name}_#{namespace}" |> String.to_atom() Bonny.PeriodicTask.unregister(task_name) - Bonny.PeriodicTask.new(task_name, {Drowzee.TransitionMonitor, :monitor_transition, [name, namespace, 1]}, 30000) + Bonny.PeriodicTask.new(task_name, {Drowzee.TransitionMonitor, :monitor_transition, [name, namespace, 1]}, 5000) end def monitor_transition(name, namespace, attempt) do diff --git a/lib/drowzee/v1beta1/sleep_schedule.ex b/lib/drowzee/v1beta1/sleep_schedule.ex index 49ae6cd..075b4a1 100644 --- a/lib/drowzee/v1beta1/sleep_schedule.ex +++ b/lib/drowzee/v1beta1/sleep_schedule.ex @@ -92,7 +92,7 @@ defmodule Drowzee.API.V1Beta1.SleepSchedule do } }, additionalPrinterColumns: [ - %{name: "Enabled", type: :string, description: "Enabled", jsonPath: ".spec.enabled"}, + %{name: "Enabled", type: :boolean, description: "Enabled", jsonPath: ".spec.enabled"}, %{name: "SleepTime", type: :string, description: "Starts Sleeping", jsonPath: ".spec.sleepTime"}, %{name: "WakeTime", type: :string, description: "Wakes Up", jsonPath: ".spec.wakeTime"}, %{name: "Timezone", type: :string, description: "Timezone", jsonPath: ".spec.timezone"}, From e894c1d2b5d7a44c626be8c55df37534899f6dc2 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 12:32:07 +0200 Subject: [PATCH 46/62] fix: Remove unused metadata --- config/config.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.exs b/config/config.exs index 983d09b..621b1c8 100644 --- a/config/config.exs +++ b/config/config.exs @@ -48,7 +48,7 @@ config :tailwind, # Configures Elixir's Logger config :logger, :console, format: "$time [$level] $message $metadata\n", - metadata: [:name, :namespace, :replicas, :configmap_name, :schedule, :request_id] + metadata: [:name, :namespace, :replicas, :schedule, :request_id] # Use Jason for JSON parsing in Phoenix config :phoenix, :json_library, Jason From 94d8f750cb214201ab7b2e4a437e61f5f26c67ef Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 13:05:50 +0200 Subject: [PATCH 47/62] feat: Update ui for enabled/disabled --- lib/drowzee_web/live/home_live/index.html.heex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/drowzee_web/live/home_live/index.html.heex b/lib/drowzee_web/live/home_live/index.html.heex index 87e0306..7168e5a 100644 --- a/lib/drowzee_web/live/home_live/index.html.heex +++ b/lib/drowzee_web/live/home_live/index.html.heex @@ -128,16 +128,16 @@ phx-value-namespace={sleep_schedule["metadata"]["namespace"]} class={ if sleep_schedule["spec"]["enabled"] == false do - "px-4 py-2 text-sm rounded-lg bg-gray-400 text-white hover:bg-gray-500" + "px-4 py-2 text-sm rounded-lg bg-green-500 text-white hover:bg-green-600" else - "px-4 py-2 text-sm rounded-lg bg-yellow-400 text-white hover:bg-yellow-500" + "px-4 py-2 text-sm rounded-lg bg-red-500 text-white hover:bg-red-600" end } > <%= if sleep_schedule["spec"]["enabled"] == false do %> - ▶️ Enable + ▷ Enable <% else %> - ⏸️ Disable + || Disable <% end %>
      From 37f9e37d635ed08cc763c9d9cc0a984394d29280 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 14:26:20 +0200 Subject: [PATCH 48/62] feat: Remove detailed information from main page --- .../live/home_live/index.html.heex | 59 +------------------ 1 file changed, 2 insertions(+), 57 deletions(-) diff --git a/lib/drowzee_web/live/home_live/index.html.heex b/lib/drowzee_web/live/home_live/index.html.heex index 7168e5a..2738c27 100644 --- a/lib/drowzee_web/live/home_live/index.html.heex +++ b/lib/drowzee_web/live/home_live/index.html.heex @@ -79,16 +79,11 @@ <%= if @namespace == nil do %> <.link navigate={~p"/#{sleep_schedule["metadata"]["namespace"]}"}> <%= String.capitalize(sleep_schedule["metadata"]["namespace"]) %> - / + / <% end %> <.link navigate={~p"/#{sleep_schedule["metadata"]["namespace"]}/#{sleep_schedule["metadata"]["name"]}"}> <%= String.capitalize(sleep_schedule["metadata"]["name"]) %> - - <%= if sleep_schedule_host(sleep_schedule) != nil do %> - <.link href={"http://#{sleep_schedule_host(sleep_schedule)}"} target="_blank"> - <.icon name="hero-arrow-top-right-on-square" class="inline-block w-6 h-6 ml-1 mb-2 text-gray-800" /> - - <% end %> +
      <%= if get_condition(sleep_schedule, "Sleeping")["status"] == "True" do %> @@ -170,56 +165,6 @@
      <% end %> -
      -

      📦 Deployments:

      -
        - <%= for deployment <- sleep_schedule["spec"]["deployments"] do %> -
      • - <% d = @deployments_by_name[deployment["name"]] %> - <%= if d && (Map.get(d["spec"], "replicas", 0) > 0) do %> - 🚀 - <% else %> - 💤 - <% end %> - <%= deployment["name"] %> -
      • - <% end %> -
      -
      - -
      -

      📦 StatefulSets:

      -
        - <%= for statefulset <- sleep_schedule["spec"]["statefulsets"] || [] do %> -
      • - <% s = @statefulsets_by_name[statefulset["name"]] %> - <%= if s && (Map.get(s["spec"], "replicas", 0) > 0) do %> - 🚀 - <% else %> - 💤 - <% end %> - <%= statefulset["name"] %> -
      • - <% end %> -
      -
      - -
      -

      📦 CronJobs:

      -
        - <%= for cronjob <- sleep_schedule["spec"]["cronjobs"] || [] do %> -
      • - <% c = @cronjobs_by_name[cronjob["name"]] %> - <%= if c && Map.get(c["spec"], "suspend", false) == false do %> - 🚀 - <% else %> - 💤 - <% end %> - <%= cronjob["name"] %> -
      • - <% end %> -
      -
      <%= if get_condition(sleep_schedule, "Error")["status"] == "True" do %>
      From 53022851ddd6d5859fe5902a18bc2c7413906184 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 14:31:25 +0200 Subject: [PATCH 49/62] fix: Rewrite logic for main page --- lib/drowzee_web/live/home_live/index.ex | 66 +++++++++++++------------ 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/lib/drowzee_web/live/home_live/index.ex b/lib/drowzee_web/live/home_live/index.ex index 8559e53..9638907 100644 --- a/lib/drowzee_web/live/home_live/index.ex +++ b/lib/drowzee_web/live/home_live/index.ex @@ -199,37 +199,40 @@ defmodule DrowzeeWeb.HomeLive.Index do [Drowzee.K8s.get_sleep_schedule!(name, socket.assigns.namespace)] end - # Gather all deployments for all schedules - deployments = - sleep_schedules - |> Enum.flat_map(fn schedule -> - case Drowzee.K8s.SleepSchedule.get_deployments(schedule) do - {:ok, ds} -> ds - {:error, _} -> [] - end - end) - - statefulsets = - sleep_schedules - |> Enum.flat_map(fn schedule -> - case Drowzee.K8s.SleepSchedule.get_statefulsets(schedule) do - {:ok, ss} -> ss - {:error, _} -> [] - end - end) - - cronjobs = - sleep_schedules - |> Enum.flat_map(fn schedule -> - case Drowzee.K8s.SleepSchedule.get_cronjobs(schedule) do - {:ok, cs} -> cs - {:error, _} -> [] - end - end) - - deployments_by_name = Map.new(deployments, &{&1["metadata"]["name"], &1}) - statefulsets_by_name = Map.new(statefulsets, &{&1["metadata"]["name"], &1}) - cronjobs_by_name = Map.new(cronjobs, &{&1["metadata"]["name"], &1}) + {deployments_by_name, statefulsets_by_name, cronjobs_by_name} = + case socket.assigns.name do + nil -> {%{}, %{}, %{}} + _name -> + deployments = + sleep_schedules + |> Enum.flat_map(fn schedule -> + case Drowzee.K8s.SleepSchedule.get_deployments(schedule) do + {:ok, ds} -> ds + {:error, _} -> [] + end + end) + statefulsets = + sleep_schedules + |> Enum.flat_map(fn schedule -> + case Drowzee.K8s.SleepSchedule.get_statefulsets(schedule) do + {:ok, ss} -> ss + {:error, _} -> [] + end + end) + cronjobs = + sleep_schedules + |> Enum.flat_map(fn schedule -> + case Drowzee.K8s.SleepSchedule.get_cronjobs(schedule) do + {:ok, cs} -> cs + {:error, _} -> [] + end + end) + { + Map.new(deployments, &{&1["metadata"]["name"], &1}), + Map.new(statefulsets, &{&1["metadata"]["name"], &1}), + Map.new(cronjobs, &{&1["metadata"]["name"], &1}) + } + end socket |> assign(:sleep_schedules, sleep_schedules) @@ -237,6 +240,7 @@ defmodule DrowzeeWeb.HomeLive.Index do |> assign(:statefulsets_by_name, statefulsets_by_name) |> assign(:cronjobs_by_name, cronjobs_by_name) |> filter_sleep_schedules(socket.assigns.search) + end def sleep_schedule_host(sleep_schedule) do From 7d9fc46d8a04e6fffab9b89cb3362422eb0e1acf Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 14:49:09 +0200 Subject: [PATCH 50/62] fix: Restore ui --- .github/workflows/pipeline-build.yml | 11 +++- .../live/home_live/index.html.heex | 56 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pipeline-build.yml b/.github/workflows/pipeline-build.yml index 82bd25f..05147fb 100644 --- a/.github/workflows/pipeline-build.yml +++ b/.github/workflows/pipeline-build.yml @@ -78,7 +78,16 @@ jobs: run: | git clone --branch gh-pages https://github.com/${{ github.repository_owner }}/drowzee.git existing-charts || true mkdir -p packaged-charts - cp -n existing-charts/*.tgz packaged-charts/ || true + + if [[ "${GITHUB_REF##*/}" == "main" ]]; then + for file in existing-charts/*; do + if [[ ! $(basename "$file") == drowzee-${{ needs.generate-version.outputs.chart-version }}* ]]; then + cp "$file" packaged-charts/ + fi + done + else + cp -n existing-charts/*.tgz packaged-charts/ || true + fi - name: Set up Helm uses: azure/setup-helm@v4 - name: Package Chart diff --git a/lib/drowzee_web/live/home_live/index.html.heex b/lib/drowzee_web/live/home_live/index.html.heex index 2738c27..c29bff7 100644 --- a/lib/drowzee_web/live/home_live/index.html.heex +++ b/lib/drowzee_web/live/home_live/index.html.heex @@ -84,6 +84,12 @@ <.link navigate={~p"/#{sleep_schedule["metadata"]["namespace"]}/#{sleep_schedule["metadata"]["name"]}"}> <%= String.capitalize(sleep_schedule["metadata"]["name"]) %> + + <%= if sleep_schedule_host(sleep_schedule) != nil do %> + <.link href={"http://#{sleep_schedule_host(sleep_schedule)}"} target="_blank"> + <.icon name="hero-arrow-top-right-on-square" class="inline-block w-6 h-6 ml-1 mb-2 text-gray-800" /> + + <% end %>
      <%= if get_condition(sleep_schedule, "Sleeping")["status"] == "True" do %> @@ -165,6 +171,56 @@
      <% end %> +
      +

      📦 Deployments:

      +
        + <%= for deployment <- sleep_schedule["spec"]["deployments"] do %> +
      • + <% d = @deployments_by_name[deployment["name"]] %> + <%= if d && (Map.get(d["spec"], "replicas", 0) > 0) do %> + 🚀 + <% else %> + 💤 + <% end %> + <%= deployment["name"] %> +
      • + <% end %> +
      +
      + +
      +

      📦 StatefulSets:

      +
        + <%= for statefulset <- sleep_schedule["spec"]["statefulsets"] || [] do %> +
      • + <% s = @statefulsets_by_name[statefulset["name"]] %> + <%= if s && (Map.get(s["spec"], "replicas", 0) > 0) do %> + 🚀 + <% else %> + 💤 + <% end %> + <%= statefulset["name"] %> +
      • + <% end %> +
      +
      + +
      +

      📦 CronJobs:

      +
        + <%= for cronjob <- sleep_schedule["spec"]["cronjobs"] || [] do %> +
      • + <% c = @cronjobs_by_name[cronjob["name"]] %> + <%= if c && Map.get(c["spec"], "suspend", false) == false do %> + 🚀 + <% else %> + 💤 + <% end %> + <%= cronjob["name"] %> +
      • + <% end %> +
      +
      <%= if get_condition(sleep_schedule, "Error")["status"] == "True" do %>
      From 2cccd4fac6584a9b99c33900f90fe845d2675164 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 14:53:19 +0200 Subject: [PATCH 51/62] fix: Update html code --- lib/drowzee_web/live/home_live/index.html.heex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/drowzee_web/live/home_live/index.html.heex b/lib/drowzee_web/live/home_live/index.html.heex index c29bff7..a2d75b3 100644 --- a/lib/drowzee_web/live/home_live/index.html.heex +++ b/lib/drowzee_web/live/home_live/index.html.heex @@ -84,7 +84,6 @@ <.link navigate={~p"/#{sleep_schedule["metadata"]["namespace"]}/#{sleep_schedule["metadata"]["name"]}"}> <%= String.capitalize(sleep_schedule["metadata"]["name"]) %> - <%= if sleep_schedule_host(sleep_schedule) != nil do %> <.link href={"http://#{sleep_schedule_host(sleep_schedule)}"} target="_blank"> <.icon name="hero-arrow-top-right-on-square" class="inline-block w-6 h-6 ml-1 mb-2 text-gray-800" /> From 0a9cf580ee649405660d9ba9db13727b3211fbf9 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 14:56:00 +0200 Subject: [PATCH 52/62] feat: Show componencts only if not empty --- lib/drowzee_web/live/home_live/index.html.heex | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/drowzee_web/live/home_live/index.html.heex b/lib/drowzee_web/live/home_live/index.html.heex index a2d75b3..9a03722 100644 --- a/lib/drowzee_web/live/home_live/index.html.heex +++ b/lib/drowzee_web/live/home_live/index.html.heex @@ -170,6 +170,7 @@
      <% end %> + <%= if sleep_schedule["spec"]["deployments"] != [] do %>

      📦 Deployments:

        @@ -186,7 +187,9 @@ <% end %>
      + <% end %> + <%= if (sleep_schedule["spec"]["statefulsets"] || []) != [] do %>

      📦 StatefulSets:

        @@ -203,7 +206,9 @@ <% end %>
      + <% end %> + <%= if (sleep_schedule["spec"]["cronjobs"] || []) != [] do %>

      📦 CronJobs:

        @@ -220,6 +225,7 @@ <% end %>
      + <% end %> <%= if get_condition(sleep_schedule, "Error")["status"] == "True" do %>
      From efb02951f3d39609195b4bf92386645e95cdad0e Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 17:12:52 +0200 Subject: [PATCH 53/62] feat: Remove apps icons from all pages except schedule page --- .../controllers/sleep_schedule_controller.ex | 4 +-- lib/drowzee/sleep_checker.ex | 2 +- lib/drowzee/transition_monitor.ex | 2 ++ .../live/home_live/index.html.heex | 30 +++++++++++-------- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/lib/drowzee/controllers/sleep_schedule_controller.ex b/lib/drowzee/controllers/sleep_schedule_controller.ex index 2ae024b..c294a3b 100644 --- a/lib/drowzee/controllers/sleep_schedule_controller.ex +++ b/lib/drowzee/controllers/sleep_schedule_controller.ex @@ -21,11 +21,11 @@ defmodule Drowzee.Controller.SleepScheduleController do Logger.info("Schedule is disabled. Skipping...") axn else - handle_event_enabled(axn, _opts) + handle_event_enabled(axn) end end - def handle_event_enabled(%Bonny.Axn{action: action} = axn, _opts) + def handle_event_enabled(%Bonny.Axn{action: action} = axn) when action in [:add, :modify, :reconcile] do Logger.metadata( schedule: axn.resource["metadata"]["name"], diff --git a/lib/drowzee/sleep_checker.ex b/lib/drowzee/sleep_checker.ex index 9902b7d..f40adf9 100644 --- a/lib/drowzee/sleep_checker.ex +++ b/lib/drowzee/sleep_checker.ex @@ -1,7 +1,7 @@ defmodule Drowzee.SleepChecker do require Logger - def naptime?(sleep_time, nil, timezone) do + def naptime?(_sleep_time, nil, _timezone) do # If wake_time is nil, always consider it naptime (always sleeping) Logger.debug("Wake time is not defined, resources will remain scaled down/suspended indefinitely") {:ok, true} diff --git a/lib/drowzee/transition_monitor.ex b/lib/drowzee/transition_monitor.ex index 1db000a..b182af9 100644 --- a/lib/drowzee/transition_monitor.ex +++ b/lib/drowzee/transition_monitor.ex @@ -17,6 +17,8 @@ defmodule Drowzee.TransitionMonitor do Bonny.PeriodicTask.new(task_name, {Drowzee.TransitionMonitor, :monitor_transition, [name, namespace, 1]}, 5000) end + @spec monitor_transition(binary(), :all | binary(), any()) :: + {:ok, [:all | binary() | number(), ...]} | {:stop, <<_::152, _::_*192>>} def monitor_transition(name, namespace, attempt) do sleep_schedule = Drowzee.K8s.get_sleep_schedule!(name, namespace) case {Drowzee.K8s.SleepSchedule.get_condition(sleep_schedule, "Transitioning"), attempt} do diff --git a/lib/drowzee_web/live/home_live/index.html.heex b/lib/drowzee_web/live/home_live/index.html.heex index 9a03722..fc8e588 100644 --- a/lib/drowzee_web/live/home_live/index.html.heex +++ b/lib/drowzee_web/live/home_live/index.html.heex @@ -177,10 +177,12 @@ <%= for deployment <- sleep_schedule["spec"]["deployments"] do %>
    • <% d = @deployments_by_name[deployment["name"]] %> - <%= if d && (Map.get(d["spec"], "replicas", 0) > 0) do %> - 🚀 - <% else %> - 💤 + <%= if @name do %> + <%= if d && (Map.get(d["spec"], "replicas", 0) > 0) do %> + 🚀 + <% else %> + 💤 + <% end %> <% end %> <%= deployment["name"] %>
    • @@ -196,10 +198,12 @@ <%= for statefulset <- sleep_schedule["spec"]["statefulsets"] || [] do %>
    • <% s = @statefulsets_by_name[statefulset["name"]] %> - <%= if s && (Map.get(s["spec"], "replicas", 0) > 0) do %> - 🚀 - <% else %> - 💤 + <%= if @name do %> + <%= if s && (Map.get(s["spec"], "replicas", 0) > 0) do %> + 🚀 + <% else %> + 💤 + <% end %> <% end %> <%= statefulset["name"] %>
    • @@ -215,10 +219,12 @@ <%= for cronjob <- sleep_schedule["spec"]["cronjobs"] || [] do %>
    • <% c = @cronjobs_by_name[cronjob["name"]] %> - <%= if c && Map.get(c["spec"], "suspend", false) == false do %> - 🚀 - <% else %> - 💤 + <%= if @name do %> + <%= if c && Map.get(c["spec"], "suspend", false) == false do %> + 🚀 + <% else %> + 💤 + <% end %> <% end %> <%= cronjob["name"] %>
    • From 642a0d710e0988214c462ca38ac255cb96881533 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 17:19:30 +0200 Subject: [PATCH 54/62] fix: Small fixes for the workflow --- .github/workflows/pipeline-build.yml | 2 +- .gitignore | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pipeline-build.yml b/.github/workflows/pipeline-build.yml index 05147fb..4646bbf 100644 --- a/.github/workflows/pipeline-build.yml +++ b/.github/workflows/pipeline-build.yml @@ -81,7 +81,7 @@ jobs: if [[ "${GITHUB_REF##*/}" == "main" ]]; then for file in existing-charts/*; do - if [[ ! $(basename "$file") == drowzee-${{ needs.generate-version.outputs.chart-version }}* ]]; then + if [[ ! $(basename "$file") == drowzee-${{ needs.generate-version.outputs.chart-version }}-* ]]; then cp "$file" packaged-charts/ fi done diff --git a/.gitignore b/.gitignore index 87d00ac..cc98d4d 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,24 @@ drowzee-*.tar npm-debug.log /assets/node_modules/ +# Elixir Language Server +.elixir_ls/ + +# Environment files +.env +.env.* + +# Editor swap/backup files +*.swp +*.swo +*.bak + +# Generic log files +*.log + +# macOS .DS_Store + +# Local config/log files local_values.yaml -dorwzee-logs.txt +drowzee-logs.txt From aedf3aeccdbe0a93b8c14ad51867e53fae8b753c Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 17:54:26 +0200 Subject: [PATCH 55/62] feat: Add Table of Contents --- .../live/home_live/index.html.heex | 98 ++++++++++++------- 1 file changed, 65 insertions(+), 33 deletions(-) diff --git a/lib/drowzee_web/live/home_live/index.html.heex b/lib/drowzee_web/live/home_live/index.html.heex index fc8e588..2968bb8 100644 --- a/lib/drowzee_web/live/home_live/index.html.heex +++ b/lib/drowzee_web/live/home_live/index.html.heex @@ -1,4 +1,21 @@ -
      +
      + + + + +
      +
      <.link navigate={~p"/"} class="flex items-center"> Logo @@ -23,40 +40,54 @@
      -
      - <%= if @namespace == nil do %> -

      All namespaces

      - <% else %> -
      -

      - <.link navigate={~p"/#{@namespace}"}> - <%= String.capitalize(@namespace) %> Namespace - - <%= if @name != nil do %> - / <%= String.capitalize(@name) %> +
      + <%= if @namespace == nil do %> +

      All namespaces

      + <% else %> +
      +

      + <.link navigate={~p"/#{@namespace}"}> + <%= String.capitalize(@namespace) %> Namespace + + <%= if @name != nil do %> + / <%= String.capitalize(@name) %> + <% end %> +

      + <%= if @sleep_schedules != [] do %> +
      + <.link phx-click="sleep_all_schedules" + phx-value-namespace={@namespace} + data-confirm={"Are you sure you want to sleep all schedules in the #{@namespace} namespace?"} + class="px-4 py-2 text-sm rounded-lg bg-blue-500 text-white hover:bg-blue-600 disabled:bg-blue-300 disabled:cursor-not-allowed"> + 😴 Sleep All + + <.link phx-click="wake_all_schedules" + phx-value-namespace={@namespace} + data-confirm={"Are you sure you want to wake all schedules in the #{@namespace} namespace?"} + class="px-4 py-2 text-sm rounded-lg bg-green-500 text-white hover:bg-green-600 disabled:bg-green-300 disabled:cursor-not-allowed"> + ☕️ Wake All + +
      <% end %> -

      - <%= if @sleep_schedules != [] do %> -
      - <.link phx-click="sleep_all_schedules" - phx-value-namespace={@namespace} - data-confirm={"Are you sure you want to sleep all schedules in the #{@namespace} namespace?"} - class="px-4 py-2 text-sm rounded-lg bg-blue-500 text-white hover:bg-blue-600 disabled:bg-blue-300 disabled:cursor-not-allowed"> - 😴 Sleep All - - <.link phx-click="wake_all_schedules" - phx-value-namespace={@namespace} - data-confirm={"Are you sure you want to wake all schedules in the #{@namespace} namespace?"} - class="px-4 py-2 text-sm rounded-lg bg-green-500 text-white hover:bg-green-600 disabled:bg-green-300 disabled:cursor-not-allowed"> - ☕️ Wake All -
      - <% end %> + <% end %>
      - <% end %> -
      - +
      \ No newline at end of file From 140e4f503a530965e86b3e0475288f293e564364 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 18:23:25 +0200 Subject: [PATCH 56/62] feat: redesign table of contents with cloud-shaped floating UI and mobile toggle --- .../live/home_live/index.html.heex | 91 +++++++++++++------ 1 file changed, 62 insertions(+), 29 deletions(-) diff --git a/lib/drowzee_web/live/home_live/index.html.heex b/lib/drowzee_web/live/home_live/index.html.heex index 2968bb8..216d282 100644 --- a/lib/drowzee_web/live/home_live/index.html.heex +++ b/lib/drowzee_web/live/home_live/index.html.heex @@ -1,20 +1,6 @@ -
      - - - +
      -
      +
      <.link navigate={~p"/"} class="flex items-center"> @@ -73,19 +59,66 @@ <% end %>
      - - + +
      +
      +
      + + +
      +
      +
      + +
        <%= if @sleep_schedules == [] do %> From 2aebbcefc891d41853b9cf648c3a7d445e3853c4 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 18:29:51 +0200 Subject: [PATCH 57/62] feat: improve table of contents layout and responsiveness in home page --- .../live/home_live/index.html.heex | 76 +++++++++---------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/lib/drowzee_web/live/home_live/index.html.heex b/lib/drowzee_web/live/home_live/index.html.heex index 216d282..545f8a0 100644 --- a/lib/drowzee_web/live/home_live/index.html.heex +++ b/lib/drowzee_web/live/home_live/index.html.heex @@ -1,6 +1,41 @@ -
        +
        + +
        +
        + + +
        +
        + -
        +
        <.link navigate={~p"/"} class="flex items-center"> @@ -59,43 +94,6 @@ <% end %>
        - -
        -
        -
        - - -
        -
        -
        -
          - <%= if @sleep_schedules == [] do %> -
        • -
          -

          - No sleep schedules found -

          -
          -
        • - <% end %> + <%= if @sleep_schedules == [] do %> +
        • +
          +

          + No sleep schedules found +

          +
          +
        • + <% end %> - <%= for sleep_schedule <- @filtered_sleep_schedules || @sleep_schedules do %> -
        • -
          -

          - <%= if get_condition(sleep_schedule, "Sleeping")["status"] == "True" do %> - 🛌 - <% else %> - 😃 - <% end %> - <%= if @namespace == nil do %> - <.link navigate={~p"/#{sleep_schedule["metadata"]["namespace"]}"}> - <%= String.capitalize(sleep_schedule["metadata"]["namespace"]) %> - / - <% end %> - <.link navigate={~p"/#{sleep_schedule["metadata"]["namespace"]}/#{sleep_schedule["metadata"]["name"]}"}> - <%= String.capitalize(sleep_schedule["metadata"]["name"]) %> - - <%= if sleep_schedule_host(sleep_schedule) != nil do %> - <.link href={"http://#{sleep_schedule_host(sleep_schedule)}"} target="_blank"> - <.icon name="hero-arrow-top-right-on-square" class="inline-block w-6 h-6 ml-1 mb-2 text-gray-800" /> - - <% end %> -

          -
          - <%= if get_condition(sleep_schedule, "Sleeping")["status"] == "True" do %> - <%= if get_condition(sleep_schedule, "Transitioning")["status"] == "True" do %> - + <%= for sleep_schedule <- @filtered_sleep_schedules || @sleep_schedules do %> +
        • +
          +

          + <%= if get_condition(sleep_schedule, "Sleeping")["status"] == "True" do %> + 🛌 <% else %> - <.link - phx-click="wake_schedule" - phx-value-name={sleep_schedule["metadata"]["name"]} - phx-value-namespace={sleep_schedule["metadata"]["namespace"]} - class="px-4 py-2 text-sm rounded-lg bg-green-500 text-white hover:bg-green-600 disabled:bg-green-300 disabled:cursor-not-allowed" - > - ☕️ Wake Up + 😃 + <% end %> + <%= if @namespace == nil do %> + <.link navigate={~p"/#{sleep_schedule["metadata"]["namespace"]}"}> + {String.capitalize(sleep_schedule["metadata"]["namespace"])} + / <% end %> - <% else %> - <%= if get_condition(sleep_schedule, "Transitioning")["status"] == "True" do %> - - <% else %> - <.link - phx-click="sleep_schedule" - phx-value-name={sleep_schedule["metadata"]["name"]} - phx-value-namespace={sleep_schedule["metadata"]["namespace"]} - class="px-4 py-2 text-sm rounded-lg bg-blue-500 text-white hover:bg-blue-600 disabled:bg-blue-300 disabled:cursor-not-allowed" - > - 💤 Sleep + <.link navigate={ + ~p"/#{sleep_schedule["metadata"]["namespace"]}/#{sleep_schedule["metadata"]["name"]}" + }> + {String.capitalize(sleep_schedule["metadata"]["name"])} + + <%= if sleep_schedule_host(sleep_schedule) != nil do %> + <.link href={"http://#{sleep_schedule_host(sleep_schedule)}"} target="_blank"> + <.icon + name="hero-arrow-top-right-on-square" + class="inline-block w-6 h-6 ml-1 mb-2 text-gray-800" + /> <% end %> - <% end %> - <.link - phx-click="toggle_enabled" - phx-value-name={sleep_schedule["metadata"]["name"]} - phx-value-namespace={sleep_schedule["metadata"]["namespace"]} - class={ - if sleep_schedule["spec"]["enabled"] == false do - "px-4 py-2 text-sm rounded-lg bg-green-500 text-white hover:bg-green-600" - else - "px-4 py-2 text-sm rounded-lg bg-red-500 text-white hover:bg-red-600" - end - } - > - <%= if sleep_schedule["spec"]["enabled"] == false do %> - ▷ Enable +

          +
          + <%= if get_condition(sleep_schedule, "Sleeping")["status"] == "True" do %> + <%= if get_condition(sleep_schedule, "Transitioning")["status"] == "True" do %> + + <% else %> + <.link + phx-click="wake_schedule" + phx-value-name={sleep_schedule["metadata"]["name"]} + phx-value-namespace={sleep_schedule["metadata"]["namespace"]} + class="px-4 py-2 text-sm rounded-lg bg-green-500 text-white hover:bg-green-600 disabled:bg-green-300 disabled:cursor-not-allowed" + > + ☕️ Wake Up + + <% end %> <% else %> - || Disable + <%= if get_condition(sleep_schedule, "Transitioning")["status"] == "True" do %> + + <% else %> + <.link + phx-click="sleep_schedule" + phx-value-name={sleep_schedule["metadata"]["name"]} + phx-value-namespace={sleep_schedule["metadata"]["namespace"]} + class="px-4 py-2 text-sm rounded-lg bg-blue-500 text-white hover:bg-blue-600 disabled:bg-blue-300 disabled:cursor-not-allowed" + > + 💤 Sleep + + <% end %> <% end %> - -
          -
          - -
          - 🕒 Schedule: - <%= sleep_schedule["spec"]["sleepTime"] %> - <%= sleep_schedule["spec"]["wakeTime"] %> (<%= sleep_schedule["spec"]["timezone"] %>) -
          - - <%= if get_condition(sleep_schedule, "ManualOverride")["status"] == "True" do %> -
          - 🛑 Manual override: - <%= case get_condition(sleep_schedule, "ManualOverride")["reason"] do - "Sleep" -> "Put to sleep " - "WakeUp" -> "Woken up " - _ -> "Manual override" - end %> at <%= last_transaction_time(sleep_schedule, "ManualOverride") %> (<%= sleep_schedule["spec"]["timezone"] %>) - <.link - phx-click="remove_override" + phx-click="toggle_enabled" phx-value-name={sleep_schedule["metadata"]["name"]} phx-value-namespace={sleep_schedule["metadata"]["namespace"]} - class="px-4 py-2 text-sm text-gray-600 hover:underline disabled:text-gray-300 disabled:cursor-not-allowed" - phx-disable-with="removing..." + class={ + if sleep_schedule["spec"]["enabled"] == false do + "px-4 py-2 text-sm rounded-lg bg-green-500 text-white hover:bg-green-600" + else + "px-4 py-2 text-sm rounded-lg bg-red-500 text-white hover:bg-red-600" + end + } > - + <%= if sleep_schedule["spec"]["enabled"] == false do %> + ▷ Enable + <% else %> + || Disable + <% end %> +
          +
        + +
        + 🕒 Schedule: + + {sleep_schedule["spec"]["sleepTime"]} - {sleep_schedule["spec"]["wakeTime"]} ({sleep_schedule[ + "spec" + ]["timezone"]})
        - <% end %> - <%= if sleep_schedule["spec"]["deployments"] != [] do %> -
        -

        📦 Deployments:

        -
          - <%= for deployment <- sleep_schedule["spec"]["deployments"] do %> -
        • - <% d = @deployments_by_name[deployment["name"]] %> - <%= if @name do %> - <%= if d && (Map.get(d["spec"], "replicas", 0) > 0) do %> - 🚀 - <% else %> - 💤 - <% end %> + <%= if get_condition(sleep_schedule, "ManualOverride")["status"] == "True" do %> +
          + 🛑 Manual override: + + {case get_condition(sleep_schedule, "ManualOverride")["reason"] do + "Sleep" -> "Put to sleep " + "WakeUp" -> "Woken up " + _ -> "Manual override" + end} at {last_transaction_time(sleep_schedule, "ManualOverride")} ({sleep_schedule[ + "spec" + ]["timezone"]}) + + + <.link + phx-click="remove_override" + phx-value-name={sleep_schedule["metadata"]["name"]} + phx-value-namespace={sleep_schedule["metadata"]["namespace"]} + class="px-4 py-2 text-sm text-gray-600 hover:underline disabled:text-gray-300 disabled:cursor-not-allowed" + phx-disable-with="removing..." + > + + + +
          + <% end %> + + <%= if sleep_schedule["spec"]["deployments"] != [] do %> +
          +

          📦 Deployments:

          +
            + <%= for deployment <- sleep_schedule["spec"]["deployments"] do %> +
          • + <% d = @deployments_by_name[deployment["name"]] %> + <%= if @name do %> + <%= if d && (Map.get(d["spec"], "replicas", 0) > 0) do %> + 🚀 + <% else %> + 💤 + <% end %> + <% end %> + {deployment["name"]} +
          • <% end %> - <%= deployment["name"] %> - - <% end %> -
          -
          - <% end %> +
        +
        + <% end %> - <%= if (sleep_schedule["spec"]["statefulsets"] || []) != [] do %> -
        -

        📦 StatefulSets:

        -
          - <%= for statefulset <- sleep_schedule["spec"]["statefulsets"] || [] do %> -
        • - <% s = @statefulsets_by_name[statefulset["name"]] %> - <%= if @name do %> - <%= if s && (Map.get(s["spec"], "replicas", 0) > 0) do %> - 🚀 - <% else %> - 💤 - <% end %> + <%= if (sleep_schedule["spec"]["statefulsets"] || []) != [] do %> +
          +

          📦 StatefulSets:

          +
            + <%= for statefulset <- sleep_schedule["spec"]["statefulsets"] || [] do %> +
          • + <% s = @statefulsets_by_name[statefulset["name"]] %> + <%= if @name do %> + <%= if s && (Map.get(s["spec"], "replicas", 0) > 0) do %> + 🚀 + <% else %> + 💤 + <% end %> + <% end %> + {statefulset["name"]} +
          • <% end %> - <%= statefulset["name"] %> - - <% end %> -
          -
          - <% end %> +
        +
        + <% end %> - <%= if (sleep_schedule["spec"]["cronjobs"] || []) != [] do %> -
        -

        📦 CronJobs:

        -
          - <%= for cronjob <- sleep_schedule["spec"]["cronjobs"] || [] do %> -
        • - <% c = @cronjobs_by_name[cronjob["name"]] %> - <%= if @name do %> - <%= if c && Map.get(c["spec"], "suspend", false) == false do %> - 🚀 - <% else %> - 💤 - <% end %> + <%= if (sleep_schedule["spec"]["cronjobs"] || []) != [] do %> +
          +

          📦 CronJobs:

          +
            + <%= for cronjob <- sleep_schedule["spec"]["cronjobs"] || [] do %> +
          • + <% c = @cronjobs_by_name[cronjob["name"]] %> + <%= if @name do %> + <%= if c && Map.get(c["spec"], "suspend", false) == false do %> + 🚀 + <% else %> + 💤 + <% end %> + <% end %> + {cronjob["name"]} +
          • <% end %> - <%= cronjob["name"] %> - - <% end %> -
          -
          - <% end %> +
        +
        + <% end %> - <%= if get_condition(sleep_schedule, "Error")["status"] == "True" do %> -
        -
        -
        -
        - 🚨 Error -
        -
        - <%= last_transaction_time(sleep_schedule, "Error") %> + <%= if get_condition(sleep_schedule, "Error")["status"] == "True" do %> +
        +
        +
        +
        + 🚨 Error +
        +
        + {last_transaction_time(sleep_schedule, "Error")} +
        +
        +
        + {get_condition(sleep_schedule, "Error")["message"]}
        -
        -
        - <%= get_condition(sleep_schedule, "Error")["message"] %>
        -
        - <% end %> - - <% end %> + <% end %> + + <% end %>
      -
      \ No newline at end of file +
      From 3734a911d3d7e6167b89e535460c460bfd35ed7e Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Thu, 8 May 2025 23:03:43 +0200 Subject: [PATCH 61/62] feat: add disabled state emoji indicator --- lib/drowzee_web/live/home_live/index.html.heex | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/drowzee_web/live/home_live/index.html.heex b/lib/drowzee_web/live/home_live/index.html.heex index e5e64a6..101880f 100644 --- a/lib/drowzee_web/live/home_live/index.html.heex +++ b/lib/drowzee_web/live/home_live/index.html.heex @@ -154,10 +154,14 @@ >

      - <%= if get_condition(sleep_schedule, "Sleeping")["status"] == "True" do %> - 🛌 + <%= if sleep_schedule["spec"]["enabled"] == false do %> + 🛑 <% else %> - 😃 + <%= if get_condition(sleep_schedule, "Sleeping")["status"] == "True" do %> + 🛌 + <% else %> + 😃 + <% end %> <% end %> <%= if @namespace == nil do %> <.link navigate={~p"/#{sleep_schedule["metadata"]["namespace"]}"}> From 310c3b38fc8b45899bdbe1afdd69f04de68f6454 Mon Sep 17 00:00:00 2001 From: Anton Bastin Date: Fri, 9 May 2025 09:32:34 +0200 Subject: [PATCH 62/62] fix: rename statefuleset.ex to correct spelling statefulset.ex --- lib/drowzee/k8s/{statefuleset.ex => statefulset.ex} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/drowzee/k8s/{statefuleset.ex => statefulset.ex} (100%) diff --git a/lib/drowzee/k8s/statefuleset.ex b/lib/drowzee/k8s/statefulset.ex similarity index 100% rename from lib/drowzee/k8s/statefuleset.ex rename to lib/drowzee/k8s/statefulset.ex