From 58a1659a6f68f33b3f1e24cc7b277857aefd946f Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 23 Aug 2025 22:57:33 -0700 Subject: [PATCH 01/12] still ugly... --- .../clusters/build_clouds/_edit.html.erb | 151 +++++++++++------- .../clusters/build_clouds/_show.html.erb | 20 +-- 2 files changed, 106 insertions(+), 65 deletions(-) diff --git a/app/views/clusters/build_clouds/_edit.html.erb b/app/views/clusters/build_clouds/_edit.html.erb index 793939be..ba46561f 100644 --- a/app/views/clusters/build_clouds/_edit.html.erb +++ b/app/views/clusters/build_clouds/_edit.html.erb @@ -1,76 +1,121 @@ <%= turbo_frame_tag dom_id(cluster, "build_cloud") do %>
-

Edit Build Cloud Configuration

- +

+ + Configure your build cloud environment +

+ <%= form_with model: build_cloud, url: cluster_build_cloud_path(cluster), method: :patch do |form| %> -
+
- <%= form.label :replicas, class: "label" do %> - Replicas - <% end %> +
+ + Replicas +
+ <%= form.number_field :replicas, class: "input input-bordered w-full", min: 1, max: 10, placeholder: "2" %>
- Number of builder replicas (1-10) + Number of parallel builders (1-10)
-
- <%= form.label :cpu_requests, class: "label" do %> - CPU Requests (millicores) - <% end %> - <%= form.number_field :cpu_requests, - class: "input input-bordered w-full", - min: 100, step: 100, - placeholder: "500" %> -
- Current: <%= integer_to_compute(build_cloud.cpu_requests) %> +
+
+ + CPU Configuration +
+ +
+
+ <%= form.label :cpu_requests, class: "label" do %> + CPU Request (millicores) + <% end %> + <%= form.number_field :cpu_requests, + class: "input input-bordered w-full", + min: 100, step: 100, + placeholder: "500" %> +
+ + The minimum amount of CPU the build cloud will request. + + + Current: <%= integer_to_compute(build_cloud.cpu_requests) %> + +
+
+ +
+ <%= form.label :cpu_limits, class: "label" do %> + CPU Limits (millicores) + <% end %> + <%= form.number_field :cpu_limits, + class: "input input-bordered w-full", + min: 100, step: 100, + placeholder: "2000" %> +
+ The maximum amount of CPU the build cloud can use. + + Current: <%= integer_to_compute(build_cloud.cpu_limits) %> + +
+
-
- <%= form.label :cpu_limits, class: "label" do %> - CPU Limits (millicores) - <% end %> - <%= form.number_field :cpu_limits, - class: "input input-bordered w-full", - min: 100, step: 100, - placeholder: "2000" %> -
- Current: <%= integer_to_compute(build_cloud.cpu_limits) %> +
+
+ + Memory Configuration
-
+
+
+ <%= form.label :memory_requests, class: "label" do %> + Memory Request (bytes) + <% end %> + <%= form.number_field :memory_requests, + class: "input input-bordered w-full", + min: 134217728, step: 134217728, + placeholder: "536870912" %> +
+ + The minimum amount of memory the build cloud will request. + + + Current: <%= integer_to_memory(build_cloud.memory_requests) %> + +
+
-
- <%= form.label :memory_requests, class: "label" do %> - Memory Requests (bytes) - <% end %> - <%= form.number_field :memory_requests, - class: "input input-bordered w-full", - min: 134217728, step: 134217728, - placeholder: "536870912" %> -
- Current: <%= integer_to_memory(build_cloud.memory_requests) %> (536870912 = 512Mi) -
-
- -
- <%= form.label :memory_limits, class: "label" do %> - Memory Limits (bytes) - <% end %> - <%= form.number_field :memory_limits, - class: "input input-bordered w-full", - min: 134217728, step: 134217728, - placeholder: "4294967296" %> -
- Current: <%= integer_to_memory(build_cloud.memory_limits) %> (4294967296 = 4Gi) +
+ <%= form.label :memory_limits, class: "label" do %> + Memory Limits (bytes) + <% end %> + <%= form.number_field :memory_limits, + class: "input input-bordered w-full", + value: build_cloud.memory_limits, + min: 134217728, step: 134217728, + placeholder: "4294967296" %> +
+ + The maximum amount of memory the build cloud can use. + + + Current: <%= integer_to_memory(build_cloud.memory_limits) %> + +
+
+
+ + Changes will require reinstalling the build cloud to take effect. +
<%= link_to cluster_build_cloud_path(cluster), class: "btn btn-ghost", @@ -81,10 +126,6 @@
<% end %> -
- - Changes will require reinstalling the build cloud to take effect. -
<% end %> \ No newline at end of file diff --git a/app/views/clusters/build_clouds/_show.html.erb b/app/views/clusters/build_clouds/_show.html.erb index 3daf09e2..5e11d2be 100644 --- a/app/views/clusters/build_clouds/_show.html.erb +++ b/app/views/clusters/build_clouds/_show.html.erb @@ -52,18 +52,18 @@ Configuration
-
-
- Replicas: - <%= build_cloud.replicas %> +
+
+
Replicas
+
<%= build_cloud.replicas %>
-
- CPU: - <%= integer_to_compute(build_cloud.cpu_requests) %>/<%= integer_to_compute(build_cloud.cpu_limits) %> +
+
CPU
+
<%= integer_to_compute(build_cloud.cpu_requests) %> / <%= integer_to_compute(build_cloud.cpu_limits) %>
-
- Memory: - <%= integer_to_memory(build_cloud.memory_requests) %>/<%= integer_to_memory(build_cloud.memory_limits) %> +
+
Memory
+
<%= integer_to_memory(build_cloud.memory_requests) %> / <%= integer_to_memory(build_cloud.memory_limits) %>
From 1c27af70d1ad542c7cc7dc193e03a10c8da82e99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 00:18:00 +0000 Subject: [PATCH 02/12] Bump httparty from 0.22.0 to 0.23.1 Bumps [httparty](https://github.com/jnunemaker/httparty) from 0.22.0 to 0.23.1. - [Release notes](https://github.com/jnunemaker/httparty/releases) - [Changelog](https://github.com/jnunemaker/httparty/blob/main/Changelog.md) - [Commits](https://github.com/jnunemaker/httparty/compare/v0.22.0...v0.23.1) --- updated-dependencies: - dependency-name: httparty dependency-version: 0.23.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 36b2d8d3..bab6f9bf 100644 --- a/Gemfile +++ b/Gemfile @@ -91,7 +91,7 @@ gem "rack", "~> 2.2" gem "tailwindcss-rails", "~> 2.7" -gem "httparty", "~> 0.22.0" +gem "httparty", "~> 0.23.1" gem "redcarpet", "~> 3.6" diff --git a/Gemfile.lock b/Gemfile.lock index f658162f..54419672 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -148,7 +148,7 @@ GEM cron2english (0.1.7) cssbundling-rails (1.4.1) railties (>= 6.0.0) - csv (3.3.0) + csv (3.3.5) database_cleaner-active_record (2.2.2) activerecord (>= 5.a) database_cleaner-core (~> 2.0) @@ -255,7 +255,7 @@ GEM http-cookie (1.0.7) domain_name (~> 0.5) http-form_data (2.3.0) - httparty (0.22.0) + httparty (0.23.1) csv mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) @@ -332,7 +332,7 @@ GEM minitest (5.25.5) msgpack (1.8.0) multi_json (1.15.0) - multi_xml (0.7.1) + multi_xml (0.7.2) bigdecimal (~> 3.1) net-http (0.6.0) uri @@ -668,7 +668,7 @@ DEPENDENCIES flipper-ui (~> 1.2.2) friendly_id (~> 5.4) good_job (~> 4.0) - httparty (~> 0.22.0) + httparty (~> 0.23.1) image_processing (~> 1.13) importmap-rails jbuilder From dc78f3e179a18fda55c6e4254a7649e80a616492 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 02:33:01 +0000 Subject: [PATCH 03/12] Bump debug from 1.10.0 to 1.11.0 Bumps [debug](https://github.com/ruby/debug) from 1.10.0 to 1.11.0. - [Release notes](https://github.com/ruby/debug/releases) - [Commits](https://github.com/ruby/debug/compare/v1.10.0...v1.11.0) --- updated-dependencies: - dependency-name: debug dependency-version: 1.11.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index f658162f..237ad33e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -154,7 +154,7 @@ GEM database_cleaner-core (~> 2.0) database_cleaner-core (2.0.1) date (3.4.1) - debug (1.10.0) + debug (1.11.0) irb (~> 1.10) reline (>= 0.3.8) devise (4.9.4) From 890921d7de58a12c7f25efbd4537e30290d9f804 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 26 Aug 2025 14:33:38 -0700 Subject: [PATCH 04/12] added styling fix --- app/components/radio_select_card_component.html.erb | 4 ++-- app/views/shared/partials/_radio_selector.html.erb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/components/radio_select_card_component.html.erb b/app/components/radio_select_card_component.html.erb index 393ffbe8..0dac15fd 100644 --- a/app/components/radio_select_card_component.html.erb +++ b/app/components/radio_select_card_component.html.erb @@ -1,8 +1,8 @@
/> -
- +
+ <%= content %>
\ No newline at end of file diff --git a/app/views/shared/partials/_radio_selector.html.erb b/app/views/shared/partials/_radio_selector.html.erb index 45e5b97f..f30d7b39 100644 --- a/app/views/shared/partials/_radio_selector.html.erb +++ b/app/views/shared/partials/_radio_selector.html.erb @@ -8,7 +8,7 @@ )) do %>
-
+

<%= option[:label] %>

<%= option[:description] %>

From 8414ebfac0efa66e0fc0bc804291c8ddf158a0c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 21:39:41 +0000 Subject: [PATCH 05/12] Bump jbuilder from 2.13.0 to 2.14.1 Bumps [jbuilder](https://github.com/rails/jbuilder) from 2.13.0 to 2.14.1. - [Release notes](https://github.com/rails/jbuilder/releases) - [Commits](https://github.com/rails/jbuilder/compare/v2.13.0...v2.14.1) --- updated-dependencies: - dependency-name: jbuilder dependency-version: 2.14.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index f658162f..d3772bc3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -277,9 +277,9 @@ GEM pp (>= 0.6.0) rdoc (>= 4.0.0) reline (>= 0.4.2) - jbuilder (2.13.0) - actionview (>= 5.0.0) - activesupport (>= 5.0.0) + jbuilder (2.14.1) + actionview (>= 7.0.0) + activesupport (>= 7.0.0) jsbundling-rails (1.3.1) railties (>= 6.0.0) json (2.13.2) From 10348ab3473f07a53ea5d4ab5b96f5e7460255ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 23:03:55 +0000 Subject: [PATCH 06/12] Bump avo from 3.23.0 to 3.23.1 Bumps [avo](https://github.com/avo-hq/avo) from 3.23.0 to 3.23.1. - [Release notes](https://github.com/avo-hq/avo/releases) - [Changelog](https://github.com/avo-hq/avo/blob/main/RELEASE.MD) - [Commits](https://github.com/avo-hq/avo/compare/v3.23.0...v3.23.1) --- updated-dependencies: - dependency-name: avo dependency-version: 3.23.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index f658162f..4cc8d4f6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -101,7 +101,7 @@ GEM activerecord (>= 3.2, < 8.0) rake (>= 10.4, < 14.0) ast (2.4.3) - avo (3.23.0) + avo (3.23.1) actionview (>= 6.1) active_link_to activerecord (>= 6.1) @@ -614,7 +614,7 @@ GEM uri (1.0.3) useragent (0.16.11) version_gem (1.1.4) - view_component (4.0.1) + view_component (4.0.2) activesupport (>= 7.1.0, < 8.1) concurrent-ruby (~> 1) warden (1.2.9) From 2d15e91eded80c7ba0b4ee4393d64f7fa1e2b52b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 20:21:34 +0000 Subject: [PATCH 07/12] Bump pagy from 9.3.4 to 9.4.0 Bumps [pagy](https://github.com/ddnexus/pagy) from 9.3.4 to 9.4.0. - [Release notes](https://github.com/ddnexus/pagy/releases) - [Changelog](https://github.com/ddnexus/pagy/blob/master/CHANGELOG.md) - [Commits](https://github.com/ddnexus/pagy/compare/9.3.4...9.4.0) --- updated-dependencies: - dependency-name: pagy dependency-version: 9.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index bab6f9bf..d1e8297b 100644 --- a/Gemfile +++ b/Gemfile @@ -79,7 +79,7 @@ gem "kubeclient", "~> 4.12" gem "light-service", "~> 0.20.0" gem "octokit", "~> 10.0" gem "omniauth-digitalocean", "~> 0.3.2" -gem "pagy", "~> 9.3" +gem "pagy", "~> 9.4" gem "rqrcode", "~> 2.2" gem "oj", "~> 3.16" gem "omniauth", "~> 2.1" diff --git a/Gemfile.lock b/Gemfile.lock index e9a3718b..5f1ea56c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -397,7 +397,7 @@ GEM omniauth (~> 2.0) orm_adapter (0.5.0) ostruct (0.6.2) - pagy (9.3.4) + pagy (9.4.0) parallel (1.27.0) parser (3.3.9.0) ast (~> 2.4.1) @@ -685,7 +685,7 @@ DEPENDENCIES omniauth-github (~> 2.0) omniauth-gitlab (~> 4.1) omniauth-rails_csrf_protection (~> 1.0) - pagy (~> 9.3) + pagy (~> 9.4) pg (~> 1.1) pretender (~> 0.3.4) pry (~> 0.15.2) From 0221e9aac3ce55a1a2633fa5f9c5958c54ba56d4 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 27 Aug 2025 15:26:29 -0700 Subject: [PATCH 08/12] added working build cloud refactor to use custom names --- .../clusters/build_clouds_controller.rb | 8 ++- app/jobs/projects/build_job.rb | 5 +- app/models/build_cloud.rb | 4 ++ app/models/user.rb | 1 + app/services/builders/base.rb | 6 +++ app/services/builders/build_cloud.rb | 20 +++++-- app/services/builders/docker.rb | 2 +- app/services/k8/base.rb | 6 +++ app/services/k8/build_cloud_manager.rb | 52 +++++++++++++++---- .../build_clouds/status_view_model.rb | 24 +++++++++ .../clusters/build_clouds/_show.html.erb | 10 +++- app/views/log_outputs/_logs.html.erb | 2 +- .../20250815234046_create_build_clouds.rb | 2 +- 13 files changed, 123 insertions(+), 19 deletions(-) create mode 100644 app/view_models/async/clusters/build_clouds/status_view_model.rb diff --git a/app/controllers/clusters/build_clouds_controller.rb b/app/controllers/clusters/build_clouds_controller.rb index b28e5bef..e83736ff 100644 --- a/app/controllers/clusters/build_clouds_controller.rb +++ b/app/controllers/clusters/build_clouds_controller.rb @@ -45,8 +45,14 @@ class Clusters::BuildCloudsController < Clusters::BaseController return end - build_cloud = @cluster.create_build_cloud! + build_cloud = if @cluster.build_cloud.nil? + @cluster.create_build_cloud! + else + @cluster.build_cloud + end + Clusters::InstallBuildCloudJob.perform_later(build_cloud) + redirect_to edit_cluster_path(@cluster), notice: "Build cloud installation started. This may take a few minutes..." end diff --git a/app/jobs/projects/build_job.rb b/app/jobs/projects/build_job.rb index 20c66cf3..071bb804 100644 --- a/app/jobs/projects/build_job.rb +++ b/app/jobs/projects/build_job.rb @@ -8,6 +8,7 @@ class Projects::BuildJob < ApplicationJob def perform(build) project = build.project + build.in_progress! # If its a container registry deploy, we don't need to build the docker image if project.container_registry? build.info("Skipping build for #{project.name} because it's a deploying from a container registry") @@ -18,17 +19,19 @@ class Projects::BuildJob < ApplicationJob # Initialize the Docker builder image_builder = if project.build_configuration&.k8s? build.info("Driver: Kubernetes (#{project.build_configuration.build_cloud.friendly_name})", color: :green) - Builders::BuildCloud.new(build) + Builders::BuildCloud.new(build, project.build_configuration.build_cloud) else build.info("Driver: Docker", color: :green) Builders::Docker.new(build) end + image_builder.setup # Login to registry image_builder.login_to_registry(project_credential_provider) # Clone repository and build clone_repository_and_build_image(project, build, image_builder) + image_builder.cleanup end complete_build!(build) diff --git a/app/models/build_cloud.rb b/app/models/build_cloud.rb index 5998102b..08896f26 100644 --- a/app/models/build_cloud.rb +++ b/app/models/build_cloud.rb @@ -53,6 +53,10 @@ class BuildCloud < ApplicationRecord "#{cluster.name} - #{namespace}" end + def name + "build-cloud-#{cluster.name}-#{id}" + end + def installation_details { namespace: namespace, diff --git a/app/models/user.rb b/app/models/user.rb index 919913ec..9a13809d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -36,6 +36,7 @@ class User < ApplicationRecord has_many :providers, dependent: :destroy has_many :clusters, through: :accounts + has_many :build_clouds, through: :clusters has_many :projects, through: :accounts has_many :add_ons, through: :accounts has_many :services, through: :accounts diff --git a/app/services/builders/base.rb b/app/services/builders/base.rb index 55ebd127..441e4e81 100644 --- a/app/services/builders/base.rb +++ b/app/services/builders/base.rb @@ -25,4 +25,10 @@ class Builders::Base raise "Docker login failed: #{stderr}" end end + + def setup + end + + def cleanup + end end diff --git a/app/services/builders/build_cloud.rb b/app/services/builders/build_cloud.rb index dad3cd6b..623e9ac7 100644 --- a/app/services/builders/build_cloud.rb +++ b/app/services/builders/build_cloud.rb @@ -4,14 +4,28 @@ require 'tempfile' require 'ostruct' module Builders - class BuildCloud < Base + class BuildCloud < Builders::Base + attr_reader :build_cloud, :build_cloud_manager + # @param connection [Object] An object that responds to #kubeconfig # @param build_cloud [BuildCloud] Optional BuildCloud model to use for namespace - def initialize(build) + def initialize(build, build_cloud) super(build) + @build_cloud = build_cloud + @build_cloud_manager = K8::BuildCloudManager.new(build_cloud.cluster, build_cloud) + end + + def setup + @build_cloud_manager.create_local_builder! + end + + def cleanup + @build_cloud_manager.remove_local_builder! end def build_image(repository_path) + @build_cloud_manager.create_local_builder! + command = construct_buildx_command(project, repository_path) runner = Cli::RunAndLog.new(build, killable: build) runner.call(command.join(" ")) @@ -19,7 +33,7 @@ module Builders def construct_buildx_command(project, repository_path) command = [ "docker", "buildx", "build" ] - command += [ "--builder", K8::BuildCloudManager::BUILDKIT_BUILDER_NAME ] + command += [ "--builder", build_cloud.name ] command += [ "--platform", "linux/amd64,linux/arm64" ] command += [ "--push" ] # Push directly to registry command += [ "--progress", "plain" ] diff --git a/app/services/builders/docker.rb b/app/services/builders/docker.rb index 63d36589..3b6d3708 100644 --- a/app/services/builders/docker.rb +++ b/app/services/builders/docker.rb @@ -3,7 +3,7 @@ require 'open3' module Builders - class Docker < Base + class Docker < Builders::Base # Build and push the Docker image def build_image(repository_path) execute_docker_build(repository_path) diff --git a/app/services/k8/base.rb b/app/services/k8/base.rb index 4613bf5a..3e300019 100644 --- a/app/services/k8/base.rb +++ b/app/services/k8/base.rb @@ -20,4 +20,10 @@ class K8::Base def client @client ||= K8::Client.new(cluster.kubeconfig) end + + def setup + end + + def cleanup + end end diff --git a/app/services/k8/build_cloud_manager.rb b/app/services/k8/build_cloud_manager.rb index 63c09826..47bcbbe1 100644 --- a/app/services/k8/build_cloud_manager.rb +++ b/app/services/k8/build_cloud_manager.rb @@ -1,7 +1,8 @@ class K8::BuildCloudManager include K8::Kubeconfig include StorageHelper - BUILDKIT_BUILDER_NAME = 'canine-k8s-builder' + # Only referenced in the migration for now. + BUILDKIT_BUILDER_DEFAULT_NAMESPACE = 'canine-k8s-builder' attr_reader :connection, :build_cloud @@ -15,7 +16,7 @@ class K8::BuildCloudManager params = { installation_metadata: { started_at: Time.current, - builder_name: K8::BuildCloudManager::BUILDKIT_BUILDER_NAME + builder_name: build_cloud.name } } @@ -67,9 +68,16 @@ class K8::BuildCloudManager @build_cloud = build_cloud end + def ensure_active! + # TODO: Check the pods in the namespace and ensure they are running. + K8::Client.from_cluster(build_cloud.cluster).pods_for_namespace(build_cloud.namespace).any? + rescue StandardError + false + end + def get_buildkit_version local_runner = Cli::RunAndReturnOutput.new - output = local_runner.call("docker buildx inspect #{K8::BuildCloudManager::BUILDKIT_BUILDER_NAME}") + output = local_runner.call("docker buildx inspect #{build_cloud.name}") if output result = parse_inspect_output(output) result[:version] @@ -94,7 +102,7 @@ class K8::BuildCloudManager status = runner.call("docker buildx ls --format json") if status.success? builder_names = runner.output.split("\n").map do |x| JSON.parse(x) end.map { |x| x["Name"] } - builder_names.include?(BUILDKIT_BUILDER_NAME) + builder_names.include?(build_cloud.name) else false end @@ -123,7 +131,33 @@ class K8::BuildCloudManager end end + def create_local_builder! + if ensure_active! + create_builder! + else + raise "Remote builder is not active, please enable the build cloud first." + end + end + + def remove_local_builder! + if ensure_active! + `docker buildx rm --keep-daemon #{build_cloud.name}` + else + raise "Remote builder is not active, please enable the build cloud first." + end + end + + def local_builder_exists? + local_runner = Cli::RunAndReturnOutput.new + local_runner.call("docker buildx inspect #{build_cloud.name}") + true + rescue StandardError + false + end + def create_builder! + return if local_builder_exists? + ensure_namespace! # Write kubeconfig to temp file for docker buildx @@ -132,15 +166,15 @@ class K8::BuildCloudManager with_kube_config do |kubeconfig_file| command = "docker buildx create " command += "--bootstrap " - command += "--name #{BUILDKIT_BUILDER_NAME} " + command += "--name #{build_cloud.name} " command += "--driver kubernetes " - command += "--driver-opt namespace=#{namespace} " + command += "--driver-opt namespace=#{build_cloud.namespace} " command += "--driver-opt replicas=#{build_cloud.replicas} " command += "--driver-opt requests.cpu=#{integer_to_compute(build_cloud.cpu_requests)} " command += "--driver-opt requests.memory=#{integer_to_memory(build_cloud.memory_requests)} " command += "--driver-opt limits.cpu=#{integer_to_compute(build_cloud.cpu_limits)} " command += "--driver-opt limits.memory=#{integer_to_memory(build_cloud.memory_limits)} " - command += "--use" + runner.call(command, envs: { "KUBECONFIG" => kubeconfig_file.path }) end @@ -170,7 +204,7 @@ class K8::BuildCloudManager end # Set the builder as active - runner.call("docker buildx use #{BUILDKIT_BUILDER_NAME}") + runner.call("docker buildx use #{build_cloud.name}") end def ensure_namespace! @@ -186,7 +220,7 @@ class K8::BuildCloudManager def remove_builder! # Delete locally, this also removes the builder from kubernetes - runner.call("docker buildx rm #{BUILDKIT_BUILDER_NAME}") + runner.call("docker buildx rm #{build_cloud.name}") # Also remove from kubernetes if possible K8::Kubectl.new(connection.kubeconfig).call("delete namespace #{namespace} --ignore-not-found=true") diff --git a/app/view_models/async/clusters/build_clouds/status_view_model.rb b/app/view_models/async/clusters/build_clouds/status_view_model.rb new file mode 100644 index 00000000..fe8f431c --- /dev/null +++ b/app/view_models/async/clusters/build_clouds/status_view_model.rb @@ -0,0 +1,24 @@ +class Async::Clusters::BuildClouds::StatusViewModel < Async::BaseViewModel + expects :build_cloud_id + + def build_cloud + @build_cloud ||= current_user.build_clouds.find(params[:build_cloud_id]) + end + + def async_render + manager = K8::BuildCloudManager.new( + build_cloud.cluster.kubeconfig, + build_cloud, + ) + if manager.ensure_active! + build_cloud.update(status: "active") + else + build_cloud.update(status: "failed") + end + render "clusters/build_clouds/status", locals: { build_cloud: } + end + + def initial_render + "
" + end +end diff --git a/app/views/clusters/build_clouds/_show.html.erb b/app/views/clusters/build_clouds/_show.html.erb index 3daf09e2..0c4a9278 100644 --- a/app/views/clusters/build_clouds/_show.html.erb +++ b/app/views/clusters/build_clouds/_show.html.erb @@ -8,7 +8,13 @@

Build Cloud

- <%= render "clusters/build_clouds/status", build_cloud: build_cloud %> + <%= render( + "shared/partials/async_renderer", + view_model: Async::Clusters::BuildClouds::StatusViewModel.new( + current_user, + build_cloud_id: build_cloud.id + ) + ) %>
<% if build_cloud.installing? %> @@ -106,7 +112,7 @@ class: "btn btn-primary btn-sm", data: { turbo_confirm: "Reinstall BuildKit on your cluster?" } do %> - <%= build_cloud.failed? ? "Retry" : "Reinstall" %> + Reinstall <% end %> <% end %>
diff --git a/app/views/log_outputs/_logs.html.erb b/app/views/log_outputs/_logs.html.erb index 4a6a7397..235d84f7 100644 --- a/app/views/log_outputs/_logs.html.erb +++ b/app/views/log_outputs/_logs.html.erb @@ -4,7 +4,7 @@ data-logs-target="container" id="<%= dom_id(loggable, :logs) %>" > - <% loggable.log_outputs.order(created_at: :asc).each do |log_output| %> + <% loggable.log_outputs.order(created_at: :desc).limit(100).reverse.each do |log_output| %> <%= render "log_outputs/log_line", log_output: log_output %> <% end %> <% if loggable.log_outputs.empty? %> diff --git a/db/migrate/20250815234046_create_build_clouds.rb b/db/migrate/20250815234046_create_build_clouds.rb index aefa5dd7..a7454e07 100644 --- a/db/migrate/20250815234046_create_build_clouds.rb +++ b/db/migrate/20250815234046_create_build_clouds.rb @@ -2,7 +2,7 @@ class CreateBuildClouds < ActiveRecord::Migration[7.2] def change create_table :build_clouds do |t| t.references :cluster, null: false, foreign_key: true - t.string :namespace, null: false, default: K8::BuildCloudManager::BUILDKIT_BUILDER_NAME + t.string :namespace, null: false, default: K8::BuildCloudManager::BUILDKIT_BUILDER_DEFAULT_NAMESPACE t.integer :status, null: false, default: 0 t.string :driver_version t.string :webhook_url From 2da9424dad14591223a447ddf1914a568b092ded Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 27 Aug 2025 16:17:57 -0700 Subject: [PATCH 09/12] remove builder everytime --- app/jobs/clusters/destroy_build_cloud_job.rb | 2 +- app/services/k8/build_cloud_manager.rb | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/app/jobs/clusters/destroy_build_cloud_job.rb b/app/jobs/clusters/destroy_build_cloud_job.rb index ff77670a..4b43d37a 100644 --- a/app/jobs/clusters/destroy_build_cloud_job.rb +++ b/app/jobs/clusters/destroy_build_cloud_job.rb @@ -17,7 +17,7 @@ module Clusters build_cloud_manager = K8::BuildCloudManager.new(cluster, build_cloud) # Teardown the builder - build_cloud_manager.teardown! + build_cloud_manager.remove_builder! # Mark the build cloud as uninstalled (keep the record for logs) build_cloud.update!( diff --git a/app/services/k8/build_cloud_manager.rb b/app/services/k8/build_cloud_manager.rb index 47bcbbe1..fb0490a7 100644 --- a/app/services/k8/build_cloud_manager.rb +++ b/app/services/k8/build_cloud_manager.rb @@ -92,11 +92,6 @@ class K8::BuildCloudManager build_cloud.namespace end - # Remove the BuildKit builder - def teardown! - remove_builder! if builder_ready? - end - # Check if the builder is ready and running def builder_ready? status = runner.call("docker buildx ls --format json") @@ -219,11 +214,10 @@ class K8::BuildCloudManager end def remove_builder! + K8::Kubectl.new(connection.kubeconfig).call("delete namespace #{namespace} --ignore-not-found=true") + # Delete locally, this also removes the builder from kubernetes runner.call("docker buildx rm #{build_cloud.name}") - - # Also remove from kubernetes if possible - K8::Kubectl.new(connection.kubeconfig).call("delete namespace #{namespace} --ignore-not-found=true") rescue StandardError => e Rails.logger.warn("Error removing builder: #{e.message}") end From ebf5fd6d12347f286218b4e8bd8a4a6555cd95a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 14:56:24 +0000 Subject: [PATCH 10/12] Bump shoulda-matchers from 6.4.0 to 6.5.0 Bumps [shoulda-matchers](https://github.com/thoughtbot/shoulda-matchers) from 6.4.0 to 6.5.0. - [Release notes](https://github.com/thoughtbot/shoulda-matchers/releases) - [Changelog](https://github.com/thoughtbot/shoulda-matchers/blob/main/CHANGELOG.md) - [Commits](https://github.com/thoughtbot/shoulda-matchers/compare/v6.4.0...v6.5.0) --- updated-dependencies: - dependency-name: shoulda-matchers dependency-version: 6.5.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index d1e8297b..a7448ce8 100644 --- a/Gemfile +++ b/Gemfile @@ -57,7 +57,7 @@ group :test do gem "database_cleaner-active_record", '~> 2.2.2' gem 'faker', '~> 3.5.2' - gem 'shoulda-matchers', '~> 6.0' + gem 'shoulda-matchers', '~> 6.5' end gem "cssbundling-rails" diff --git a/Gemfile.lock b/Gemfile.lock index cbd53218..c312c50b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -561,7 +561,7 @@ GEM sentry-ruby (5.26.0) bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) - shoulda-matchers (6.4.0) + shoulda-matchers (6.5.0) activesupport (>= 5.2.0) simplecov (0.22.0) docile (~> 1.1) @@ -703,7 +703,7 @@ DEPENDENCIES selenium-webdriver sentry-rails (~> 5.26) sentry-ruby (~> 5.23) - shoulda-matchers (~> 6.0) + shoulda-matchers (~> 6.5) simplecov sitemap_generator (~> 6.1) sprockets-rails From 73d6b97cec7a2d914478324f0692fcb640db03f0 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 29 Aug 2025 18:13:02 -0700 Subject: [PATCH 11/12] validate project namespace by checking canine managed --- .../validate_namespace_availability.rb | 2 +- .../validate_namespace_availability_spec.rb | 29 ++++++++++++++----- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/app/actions/projects/validate_namespace_availability.rb b/app/actions/projects/validate_namespace_availability.rb index a371f954..036dbba9 100644 --- a/app/actions/projects/validate_namespace_availability.rb +++ b/app/actions/projects/validate_namespace_availability.rb @@ -14,7 +14,7 @@ module Projects # Check if namespace already exists in Kubernetes namespace_exists = existing_namespaces.any? do |ns| - ns.metadata.name == project.name + ns.metadata.name == project.name && ns.metadata&.labels&.caninemanaged != "true" end if namespace_exists diff --git a/spec/actions/projects/validate_namespace_availability_spec.rb b/spec/actions/projects/validate_namespace_availability_spec.rb index 3639071e..6994bc13 100644 --- a/spec/actions/projects/validate_namespace_availability_spec.rb +++ b/spec/actions/projects/validate_namespace_availability_spec.rb @@ -23,18 +23,31 @@ RSpec.describe Projects::ValidateNamespaceAvailability do end context 'when namespace already exists' do - let(:existing_namespace) do - OpenStruct.new(metadata: OpenStruct.new(name: 'test-app')) - end - before do allow(k8_client).to receive(:get_namespaces).and_return([ existing_namespace ]) end - it 'fails with error message' do - described_class.execute(context) - expect(context).to be_failure - expect(context.message).to include("already exists") + context 'when namespace is not managed by Canine' do + let(:existing_namespace) do + OpenStruct.new(metadata: OpenStruct.new(name: 'test-app')) + end + + it 'fails with error message' do + described_class.execute(context) + expect(context).to be_failure + expect(context.message).to include("already exists") + end + end + + context 'when namespace is managed by Canine' do + let(:existing_namespace) do + OpenStruct.new(metadata: OpenStruct.new(name: 'test-app', labels: OpenStruct.new(caninemanaged: "true"))) + end + + it 'succeeds' do + described_class.execute(context) + expect(context).to be_success + end end end end From 70177e93f5cd7e5f99626baa386ba838ca4ce6e2 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 29 Aug 2025 18:22:01 -0700 Subject: [PATCH 12/12] added different drop down that should work on mobile --- app/views/layouts/_sidebar.html.erb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/layouts/_sidebar.html.erb b/app/views/layouts/_sidebar.html.erb index 388cca18..8bd4558c 100644 --- a/app/views/layouts/_sidebar.html.erb +++ b/app/views/layouts/_sidebar.html.erb @@ -7,9 +7,9 @@ <% unless Rails.application.config.local_mode %>
- +
<% end %>