adding support for k3s

This commit is contained in:
Chris Zhu
2024-12-15 00:42:39 -08:00
parent d726fd63db
commit bcb88dd985
14 changed files with 197 additions and 41 deletions

View File

@@ -34,7 +34,7 @@ class ClustersController < ApplicationController
end
def retry_install
InstallClusterJob.perform_later(@cluster)
Clusters::InstallJob.perform_later(@cluster)
redirect_to @cluster, notice: "Retrying installation for cluster..."
end
@@ -93,7 +93,7 @@ class ClustersController < ApplicationController
respond_to do |format|
if @cluster.save
# Kick off cluster job
InstallClusterJob.perform_later(@cluster)
Clusters::InstallJob.perform_later(@cluster)
format.html { redirect_to @cluster, notice: "Cluster was successfully created." }
format.json { render :show, status: :created, location: @cluster }
else
@@ -139,13 +139,30 @@ class ClustersController < ApplicationController
# Only allow a list of trusted parameters through.
def cluster_params
if params[:cluster][:kubeconfig].present?
kubeconfig_file = params[:cluster][:kubeconfig]
if params[:cluster][:cluster_type] == "k3s"
ip_address = params[:cluster][:ip_address]
kubeconfig_output = params[:cluster][:kubeconfig_output]
if ip_address.blank? || kubeconfig_output.blank?
message = "IP address and kubeconfig output are required for K3s clusters"
flash[:error] = message
raise message
end
begin
data = YAML.safe_load(kubeconfig_output)
data["clusters"][0]["cluster"]["server"] = "https://#{ip_address}:6443"
rescue StandardError => e
message = "Invalid kubeconfig output"
flash[:error] = message
raise message
end
params[:cluster][:kubeconfig] = data
elsif (kubeconfig_file = params[:cluster][:kubeconfig_file]).present?
yaml_content = kubeconfig_file.read
params[:cluster][:kubeconfig] = YAML.safe_load(yaml_content)
end
params.require(:cluster).permit(:name, kubeconfig: {})
params.require(:cluster).permit(:name, :cluster_type, kubeconfig: {})
end
end

View File

@@ -0,0 +1,14 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["instructions"]
update(event) {
// Validate it is a valid IP address
if (!/^(\d{1,3}\.){3}\d{1,3}$/.test(event.target.value)) {
return
}
this.instructionsTarget.classList.remove("hidden")
}
}

View File

@@ -1,4 +1,4 @@
class InstallClusterJob < ApplicationJob
class Clusters::InstallJob < ApplicationJob
queue_as :default
def perform(cluster)

View File

@@ -2,13 +2,14 @@
#
# Table name: clusters
#
# id :bigint not null, primary key
# kubeconfig :jsonb not null
# name :string not null
# status :integer default("initializing"), not null
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
# id :bigint not null, primary key
# cluster_type :integer default("k8s")
# kubeconfig :jsonb not null
# name :string not null
# status :integer default("initializing"), not null
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
#
# Indexes
#
@@ -37,4 +38,8 @@ class Cluster < ApplicationRecord
running: 2,
failed: 3
}
enum :cluster_type, {
k8s: 0,
k3s: 1,
}
end

View File

@@ -6,6 +6,7 @@
# allow_public_networking :boolean default(FALSE)
# command :string
# container_port :integer default(3000)
# description :text
# healthcheck_url :string
# last_health_checked_at :datetime
# name :string not null

View File

@@ -64,25 +64,33 @@ module K8
Kubeclient::Client.new(
cluster_info["server"],
"v1",
ssl_options: ssl_options(cluster_info),
ssl_options: ssl_options(user_info, cluster_info),
auth_options: auth_options(user_info)
)
end
def ssl_options(_cluster_info)
{ verify_ssl: OpenSSL::SSL::VERIFY_NONE }
def ssl_options(user_info, cluster_info)
_ssl_options = {}
if user_info["client-certificate-data"] && user_info["client-key-data"]
begin
_ssl_options[:client_key] = OpenSSL::PKey::RSA.new(Base64.decode64(user_info["client-key-data"]))
rescue OpenSSL::PKey::RSAError
_ssl_options[:client_key] = OpenSSL::PKey::EC.new(Base64.decode64(user_info["client-key-data"]))
end
_ssl_options[:client_cert] =
OpenSSL::X509::Certificate.new(Base64.decode64(user_info["client-certificate-data"]))
_ssl_options[:verify_ssl] = OpenSSL::SSL::VERIFY_NONE
end
# if cluster_info['certificate-authority-data']
# ssl_options[:ca_file] = write_temp_file(Base64.decode64(cluster_info['certificate-authority-data']))
# end
_ssl_options
end
def auth_options(user_info)
auth_options = {}
if user_info["client-certificate-data"] && user_info["client-key-data"]
auth_options[:client_cert] =
OpenSSL::X509::Certificate.new(Base64.decode64(user_info["client-certificate-data"]))
auth_options[:client_key] = OpenSSL::PKey::RSA.new(Base64.decode64(user_info["client-key-data"]))
elsif user_info["token"]
if user_info["token"]
auth_options[:bearer_token] = user_info["token"]
end
auth_options

View File

@@ -5,34 +5,27 @@
<%= form.text_field :name, class: "input input-bordered", value: RandomNameGenerator.generate_name %>
</div>
<% if defined?(show_instructions) %>
<div data-controller="new-add-ons">
<label class="mb-2">Which managed Kubernetes provider are you using?</label>
<div class="form-group">
<div class="flex gap-4">
<select class="hidden" data-new-add-ons-target="input">
<option value="digitalocean">Digital Ocean</option>
<option value="linode">Linode</option>
<option value="linode">Other</option>
</select>
<%= render "clusters/instructions/cards/digitalocean" %>
<%= render "clusters/instructions/cards/linode" %>
<%= render "clusters/instructions/cards/other" %>
<%= form.collection_select :cluster_type,
Cluster.cluster_types.keys,
:to_s,
:titleize,
{ },
{ class: "select select-bordered hidden", data: { new_add_ons_target: "input" } }
%>
<%= render "clusters/cluster_types/cards/k8s" %>
<%= render "clusters/cluster_types/cards/k3s" %>
</div>
</div>
<div class="my-10 has-styled-links">
<%= render "clusters/instructions/instructions/digitalocean" %>
<%= render "clusters/instructions/instructions/linode" %>
<%= render "clusters/instructions/instructions/other" %>
<%= render "clusters/cluster_types/instructions/k8s", form: form %>
<%= render "clusters/cluster_types/instructions/k3s", form: form %>
</div>
</div>
<% end %>
<div class="form-group">
<%= form.label :kubeconfig %>
<%= form.file_field :kubeconfig, class: "file-input w-full" %>
</div>
<div class="form-footer">
<%= form.button "Submit", class: "btn btn-primary" %>

View File

@@ -0,0 +1,29 @@
<div
class="cursor-pointer card w-64 bg-base-200"
data-card-name="k3s"
data-action="click->new-add-ons#selectCard"
data-new-add-ons-target="card"
>
<div class="card-body">
<iconify-icon icon="devicon:k3s" height="2.5rem" width="2.5rem"></iconify-icon>
<h2 class="card-title">K3s</h2>
<div class="space-y-2 text-sm text-gray-400">
<div class="flex items-center gap-2">
<iconify-icon icon="lucide:dollar-sign" class="w-4 h-4"></iconify-icon>
<span>$24 / month</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon icon="lucide:memory-stick" class="w-4 h-4"></iconify-icon>
<span>4GB RAM</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon icon="lucide:cpu" class="w-4 h-4"></iconify-icon>
<span>2 CPU</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon icon="lucide:hard-drive" class="w-4 h-4"></iconify-icon>
<span>80GB SSD</span>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,29 @@
<div
class="cursor-pointer card w-64 bg-base-200"
data-card-name="k8s"
data-action="click->new-add-ons#selectCard"
data-new-add-ons-target="card"
>
<div class="card-body">
<iconify-icon icon="devicon:kubernetes" height="2.5rem" width="2.5rem"></iconify-icon>
<h2 class="card-title">Kubernetes</h2>
<div class="space-y-2 text-sm text-gray-400">
<div class="flex items-center gap-2">
<iconify-icon icon="lucide:dollar-sign" class="w-4 h-4"></iconify-icon>
<span>$24 / month</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon icon="lucide:memory-stick" class="w-4 h-4"></iconify-icon>
<span>4GB RAM</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon icon="lucide:cpu" class="w-4 h-4"></iconify-icon>
<span>2 CPU</span>
</div>
<div class="flex items-center gap-2">
<iconify-icon icon="lucide:hard-drive" class="w-4 h-4"></iconify-icon>
<span>80GB SSD</span>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,25 @@
<div class="card-form hidden card-k3s space-y-4" data-controller="k3s-instructions">
<div class="form-group">
<%= form.label :ip_address, "IP Address" %>
<%= form.text_field :ip_address, class: "input input-bordered", data: { action: "input->k3s-instructions#update" } %>
</div>
<p data-k3s-instructions-target="instructions" class="hidden">
<div>
1. SSH into the server and run:
<pre class="block">curl -sfL https://get.k3s.io | sh -</pre>
</div>
<div>
2. Once the install is complete, run:
<pre class="block">sudo cat /etc/rancher/k3s/k3s.yaml</pre>
</div>
<div>
3. Copy the output and paste it here:
<div class="form-group">
<%= form.text_area :kubeconfig_output, class: "textarea textarea-bordered w-full max-w-lg h-48" %>
</div>
</div>
</p>
</div>

View File

@@ -0,0 +1,28 @@
<div class="card-form hidden card-k8s space-y-4">
<div data-controller="new-add-ons">
<label class="mb-2">Which managed Kubernetes provider are you using?</label>
<div class="form-group">
<div class="flex gap-4">
<select class="hidden" data-new-add-ons-target="input">
<option value="digitalocean">Digital Ocean</option>
<option value="linode">Linode</option>
<option value="linode">Other</option>
</select>
<%= render "clusters/instructions/cards/digitalocean" %>
<%= render "clusters/instructions/cards/linode" %>
<%= render "clusters/instructions/cards/other" %>
</div>
</div>
<div class="my-10 has-styled-links">
<%= render "clusters/instructions/instructions/digitalocean" %>
<%= render "clusters/instructions/instructions/linode" %>
<%= render "clusters/instructions/instructions/other" %>
</div>
</div>
<div class="form-group">
<%= form.label :kubeconfig_file %>
<%= form.file_field :kubeconfig_file, class: "file-input w-full" %>
</div>
</div>

View File

@@ -11,7 +11,7 @@
<div class="card card-bordered bg-base-100">
<div class="card-body">
<%= render "form", cluster: @cluster, show_instructions: true %>
<%= render "form", cluster: @cluster %>
</div>
</div>
</div>

View File

@@ -0,0 +1,6 @@
class AddClusterTypeToClusters < ActiveRecord::Migration[7.2]
def change
add_column :clusters, :cluster_type, :integer, default: 0
Cluster.update_all(cluster_type: :k8s)
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: 2024_12_07_234211) do
ActiveRecord::Schema[7.2].define(version: 2024_12_15_025029) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -113,6 +113,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_12_07_234211) do
t.integer "status", default: 0, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "cluster_type", default: 0
t.index ["account_id"], name: "index_clusters_on_account_id"
end