Merge pull request #501 from CanineHQ/chriszhu__update_clusters_verify

update cluster verify
This commit is contained in:
Chris Zhu
2026-01-16 17:31:20 -08:00
committed by GitHub
14 changed files with 77 additions and 85 deletions

View File

@@ -208,6 +208,6 @@ class ClustersController < ApplicationController
params[:cluster][:kubeconfig] = YAML.safe_load(yaml_content)
end
params.require(:cluster).permit(:name, :cluster_type, kubeconfig: {})
params.require(:cluster).permit(:name, :cluster_type, :skip_tls_verify, kubeconfig: {})
end
end

View File

@@ -2,16 +2,17 @@
#
# Table name: clusters
#
# id :bigint not null, primary key
# cluster_type :integer default("k8s")
# kubeconfig :jsonb
# name :string not null
# options :jsonb not null
# status :integer default("initializing"), not null
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
# external_id :string
# id :bigint not null, primary key
# cluster_type :integer default("k8s")
# kubeconfig :jsonb
# name :string not null
# options :jsonb not null
# skip_tls_verify :boolean default(FALSE), not null
# status :integer default("initializing"), not null
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
# external_id :string
#
# Indexes
#

View File

@@ -5,7 +5,6 @@ require 'ostruct'
module Builders
class BuildCloud < Builders::Base
include K8::Kubeconfig
attr_reader :build_cloud_manager
def initialize(build, build_cloud_manager)
@@ -26,15 +25,12 @@ module Builders
command = construct_buildx_command(project, repository_path)
runner = Cli::RunAndLog.new(build, killable: build)
with_kube_config do |kubeconfig_file|
connection = build_cloud_manager.connection
K8::Kubeconfig.with_kube_config(connection.kubeconfig, skip_tls_verify: connection.cluster.skip_tls_verify) do |kubeconfig_file|
runner.call(command.join(" "), envs: { "KUBECONFIG" => kubeconfig_file.path })
end
end
def kubeconfig
build_cloud_manager.connection.kubeconfig
end
def construct_buildx_command(project, repository_path)
command = [ "docker", "buildx", "build" ]
command += [ "--builder", build_cloud_manager.build_cloud.name ]

View File

@@ -1,5 +1,4 @@
class K8::BuildCloudManager
include K8::Kubeconfig
include StorageHelper
# Only referenced in the migration for now.
BUILDKIT_BUILDER_DEFAULT_NAMESPACE = 'canine-k8s-builder'
@@ -159,7 +158,7 @@ class K8::BuildCloudManager
# Create the buildx builder with kubernetes driver
# The --bootstrap flag will start the builder immediately
with_kube_config do |kubeconfig_file|
K8::Kubeconfig.with_kube_config(connection.kubeconfig, skip_tls_verify: connection.cluster.skip_tls_verify) do |kubeconfig_file|
command = "docker buildx create "
command += "--bootstrap "
command += "--name #{build_cloud.name} "
@@ -205,7 +204,7 @@ class K8::BuildCloudManager
def ensure_namespace!
# Create namespace if it doesn't exist
with_kube_config do |kubeconfig_file|
K8::Kubeconfig.with_kube_config(connection.kubeconfig, skip_tls_verify: connection.cluster.skip_tls_verify) do |kubeconfig_file|
command = "kubectl create namespace #{namespace}"
runner.call(command, envs: { "KUBECONFIG" => kubeconfig_file.path })
end
@@ -227,11 +226,6 @@ class K8::BuildCloudManager
@runner ||= Cli::RunAndLog.new(build_cloud)
end
def kubeconfig
# This is necessary for the include K8::Kubeconfig module
connection.kubeconfig
end
def parse_inspect_output(text)
version = nil

View File

@@ -1,8 +1,7 @@
class K8::Helm::Client
DEFAULT_TIMEOUT = "1000s"
CHARTS = YAML.load_file(Rails.root.join('resources', 'helm', 'charts.yml'))
include K8::Kubeconfig
attr_reader :kubeconfig, :runner
attr_reader :connection, :runner
def initialize(runner)
@runner = runner
@@ -16,17 +15,16 @@ class K8::Helm::Client
def connect(connection)
@connection = connection
@kubeconfig = connection.kubeconfig.is_a?(String) ? JSON.parse(connection.kubeconfig) : connection.kubeconfig
self
end
def connected?
@kubeconfig.present?
@connection&.kubeconfig.present?
end
def get_values_yaml(name, namespace: 'default')
return StandardError.new("Can't get current values yaml if not connected") unless connected?
with_kube_config do |kubeconfig_file|
K8::Kubeconfig.with_kube_config(connection.kubeconfig, skip_tls_verify: connection.cluster.skip_tls_verify) do |kubeconfig_file|
command = "helm get values #{name} --namespace #{namespace} --kubeconfig=#{kubeconfig_file.path}"
output = runner.(command, envs: { "KUBECONFIG" => kubeconfig_file.path })
# Remove the key USER-SUPPLIED VALUES
@@ -38,7 +36,7 @@ class K8::Helm::Client
def get_all_values_yaml(name, namespace: 'default')
return StandardError.new("Can't get all values yaml if not connected") unless connected?
with_kube_config do |kubeconfig_file|
K8::Kubeconfig.with_kube_config(connection.kubeconfig, skip_tls_verify: connection.cluster.skip_tls_verify) do |kubeconfig_file|
command = "helm get values #{name} --all --namespace #{namespace} --kubeconfig=#{kubeconfig_file.path}"
output = runner.(command, envs: { "KUBECONFIG" => kubeconfig_file.path })
output
@@ -47,7 +45,7 @@ class K8::Helm::Client
def ls
return StandardError.new("Can't list helm charts if not connected") unless connected?
with_kube_config do |kubeconfig_file|
K8::Kubeconfig.with_kube_config(connection.kubeconfig, skip_tls_verify: connection.cluster.skip_tls_verify) do |kubeconfig_file|
command_output = `helm ls --all-namespaces --kubeconfig=#{kubeconfig_file.path} -o yaml`
output = YAML.safe_load(command_output)
end
@@ -107,12 +105,14 @@ class K8::Helm::Client
wait: false,
history_max: nil,
create_namespace: false,
skip_tls_verify: K8::Kubeconfig.skip_tls_verify?,
skip_tls_verify: nil,
timeout: DEFAULT_TIMEOUT
)
return StandardError.new("Can't install helm chart if not connected") unless connected?
with_kube_config do |kubeconfig_file|
skip_tls = skip_tls_verify.nil? ? connection.cluster.skip_tls_verify : skip_tls_verify
K8::Kubeconfig.with_kube_config(connection.kubeconfig, skip_tls_verify: skip_tls) do |kubeconfig_file|
Tempfile.create([ 'values', '.yaml' ]) do |values_file|
values_file.write(values.to_yaml)
values_file.flush
@@ -129,7 +129,7 @@ class K8::Helm::Client
wait: wait,
history_max: history_max,
create_namespace: create_namespace,
skip_tls_verify: skip_tls_verify
skip_tls_verify: skip_tls
)
exit_status = runner.(command, envs: { "KUBECONFIG" => kubeconfig_file.path })
raise "`#{command}` failed with exit status #{exit_status}" unless exit_status.success?
@@ -141,7 +141,7 @@ class K8::Helm::Client
def uninstall(name, namespace: 'default')
return StandardError.new("Can't uninstall helm chart if not connected") unless connected?
with_kube_config do |kubeconfig_file|
K8::Kubeconfig.with_kube_config(connection.kubeconfig, skip_tls_verify: connection.cluster.skip_tls_verify) do |kubeconfig_file|
command = "helm uninstall #{name} --namespace #{namespace}"
exit_status = runner.(command, envs: { "KUBECONFIG" => kubeconfig_file.path })
raise "Helm uninstall failed with exit status #{exit_status}" unless exit_status.success?

View File

@@ -1,27 +1,17 @@
module K8
module Kubeconfig
def self.skip_tls_verify?
!ENV['VERIFY_CLUSTER_TLS'].present?
end
def self.skip_tls_env
skip_tls_verify? ? { "SKIP_TLS_VERIFY" => "true" } : {}
end
def with_kube_config
def self.with_kube_config(kubeconfig, skip_tls_verify: false)
Tempfile.open([ 'kubeconfig', '.yaml' ]) do |kubeconfig_file|
kubeconfig_hash = kubeconfig.is_a?(String) ? JSON.parse(kubeconfig) : kubeconfig
kubeconfig_hash = apply_tls_settings(kubeconfig_hash)
kubeconfig_hash = apply_tls_settings(kubeconfig_hash, skip_tls_verify)
kubeconfig_file.write(kubeconfig_hash.to_yaml)
kubeconfig_file.flush
yield kubeconfig_file
end
end
private
def apply_tls_settings(kubeconfig_hash)
return kubeconfig_hash if ENV['VERIFY_CLUSTER_TLS'].present?
def self.apply_tls_settings(kubeconfig_hash, skip_tls_verify)
return kubeconfig_hash unless skip_tls_verify
kubeconfig_hash = kubeconfig_hash.deep_dup
kubeconfig_hash['clusters']&.each do |cluster|

View File

@@ -1,13 +1,11 @@
# frozen_string_literal: true
class K8::Kubectl
include K8::Kubeconfig
attr_reader :kubeconfig, :runner
attr_reader :connection, :runner
def initialize(connection, runner = Cli::RunAndReturnOutput.new)
@_kubeconfig = connection.kubeconfig
@kubeconfig = @_kubeconfig.is_a?(String) ? JSON.parse(@_kubeconfig) : @_kubeconfig
if @kubeconfig.nil?
@connection = connection
if connection.kubeconfig.nil?
raise "Kubeconfig is required"
end
@runner = runner
@@ -28,7 +26,7 @@ class K8::Kubectl
block.call(yaml_content)
end
with_kube_config do |kubeconfig_file|
K8::Kubeconfig.with_kube_config(connection.kubeconfig, skip_tls_verify: connection.cluster.skip_tls_verify) do |kubeconfig_file|
# Create a temporary file for the YAML content
Tempfile.open([ "k8s", ".yaml" ]) do |yaml_file|
yaml_file.write(yaml_content)
@@ -46,7 +44,7 @@ class K8::Kubectl
end
def call(command)
with_kube_config do |kubeconfig_file|
K8::Kubeconfig.with_kube_config(connection.kubeconfig, skip_tls_verify: connection.cluster.skip_tls_verify) do |kubeconfig_file|
full_command = "kubectl #{command}"
runner.call(full_command, envs: { "KUBECONFIG" => kubeconfig_file.path })
end

View File

@@ -7,6 +7,13 @@
</label>
<% end %>
<%= render(FormFieldComponent.new(
label: "Skip TLS Verification",
description: "Enable this if your cluster uses self-signed certificates."
)) do %>
<%= form.check_box :skip_tls_verify, class: "checkbox checkbox-error" %>
<% end %>
<div class="form-footer">
<%= form.submit "Save", class: "btn btn-primary", data: { turbo_submits_with: "Saving..." } %>
</div>

View File

@@ -35,6 +35,7 @@
<%= render "clusters/cluster_types/instructions/local_k3s", form: form %>
</div>
</div>
</div>
<% end %>

View File

@@ -0,0 +1,5 @@
class AddSkipTlsVerifyToClusters < ActiveRecord::Migration[7.2]
def change
add_column :clusters, :skip_tls_verify, :boolean, default: false, null: false
end
end

3
db/schema.rb generated
View File

@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.2].define(version: 2026_01_08_142856) do
ActiveRecord::Schema[7.2].define(version: 2026_01_16_173824) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -175,6 +175,7 @@ ActiveRecord::Schema[7.2].define(version: 2026_01_08_142856) do
t.integer "cluster_type", default: 0
t.string "external_id"
t.jsonb "options", default: {}, null: false
t.boolean "skip_tls_verify", default: false, null: false
t.index ["account_id", "name"], name: "index_clusters_on_account_id_and_name", unique: true
end

View File

@@ -2,16 +2,17 @@
#
# Table name: clusters
#
# id :bigint not null, primary key
# cluster_type :integer default("k8s")
# kubeconfig :jsonb
# name :string not null
# options :jsonb not null
# status :integer default("initializing"), not null
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
# external_id :string
# id :bigint not null, primary key
# cluster_type :integer default("k8s")
# kubeconfig :jsonb
# name :string not null
# options :jsonb not null
# skip_tls_verify :boolean default(FALSE), not null
# status :integer default("initializing"), not null
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
# external_id :string
#
# Indexes
#

View File

@@ -2,16 +2,17 @@
#
# Table name: clusters
#
# id :bigint not null, primary key
# cluster_type :integer default("k8s")
# kubeconfig :jsonb
# name :string not null
# options :jsonb not null
# status :integer default("initializing"), not null
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
# external_id :string
# id :bigint not null, primary key
# cluster_type :integer default("k8s")
# kubeconfig :jsonb
# name :string not null
# options :jsonb not null
# skip_tls_verify :boolean default(FALSE), not null
# status :integer default("initializing"), not null
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
# external_id :string
#
# Indexes
#

View File

@@ -3,19 +3,16 @@ require 'rails_helper'
RSpec.describe K8::Helm::Client do
let(:runner) { instance_double(Cli::RunAndReturnOutput) }
let(:client) { described_class.new(runner) }
let(:cluster) { create(:cluster) }
let(:connection) { K8::Connection.new(cluster, nil) }
describe '#connected?' do
it 'returns false when not connected' do
expect(client).not_to be_connected
end
it 'returns false when @kubeconfig is nil' do
client.instance_variable_set(:@kubeconfig, nil)
expect(client).not_to be_connected
end
it 'returns true when @kubeconfig is present' do
client.instance_variable_set(:@kubeconfig, { "apiVersion" => "v1" })
it 'returns true when connection kubeconfig is present' do
client.connect(connection)
expect(client).to be_connected
end
end