mirror of
https://github.com/czhu12/canine.git
synced 2025-12-21 10:49:49 -06:00
adding support for k3s
This commit is contained in:
@@ -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
|
||||
|
||||
14
app/javascript/controllers/k3s_instructions_controller.js
Normal file
14
app/javascript/controllers/k3s_instructions_controller.js
Normal 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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class InstallClusterJob < ApplicationJob
|
||||
class Clusters::InstallJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(cluster)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" %>
|
||||
|
||||
29
app/views/clusters/cluster_types/cards/_k3s.html.erb
Normal file
29
app/views/clusters/cluster_types/cards/_k3s.html.erb
Normal 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>
|
||||
29
app/views/clusters/cluster_types/cards/_k8s.html.erb
Normal file
29
app/views/clusters/cluster_types/cards/_k8s.html.erb
Normal 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>
|
||||
25
app/views/clusters/cluster_types/instructions/_k3s.html.erb
Normal file
25
app/views/clusters/cluster_types/instructions/_k3s.html.erb
Normal 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>
|
||||
|
||||
28
app/views/clusters/cluster_types/instructions/_k8s.html.erb
Normal file
28
app/views/clusters/cluster_types/instructions/_k8s.html.erb
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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
3
db/schema.rb
generated
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user