diff --git a/app/actions/add_ons/install_helm_chart.rb b/app/actions/add_ons/install_helm_chart.rb index 5e4baf80..1b306df4 100644 --- a/app/actions/add_ons/install_helm_chart.rb +++ b/app/actions/add_ons/install_helm_chart.rb @@ -14,20 +14,21 @@ class AddOns::InstallHelmChart charts = K8::Helm::Client::CHARTS['helm']['charts'] chart = charts.find { |chart| chart['name'] == add_on.chart_type } - client = K8::Helm::Client.new(add_on.cluster.kubeconfig, Cli::RunAndLog.new(add_on)) + client = K8::Helm::Client.connect(add_on.cluster.kubeconfig, Cli::RunAndLog.new(add_on)) helm_chart_url = add_on.helm_chart_url if chart['add_repo_command'] - client.add_repo(chart['add_repo_command']) + client.run_command(chart['add_repo_command']) client.repo_update! - end - if add_on.helm_chart? + elsif add_on.helm_chart? # Special case for helm_chart, we need to add the repo and update it package_details = add_on.metadata['package_details'] - add_repo_command = "helm repo add #{package_details['name']} #{package_details['repository']['url']}" - client.add_repo(add_repo_command) + client.add_repo( + package_details['name'], + package_details['repository']['url'] + ) client.repo_update! - helm_chart_url = "#{package_details['repository']['organization_name']}/#{package_details['repository']['name']}" + helm_chart_url = "#{package_details['repository']['organization_name']}/#{package_details['name']}" end client.install( diff --git a/app/actions/add_ons/uninstall_helm_chart.rb b/app/actions/add_ons/uninstall_helm_chart.rb index b1ceaf9d..9cf152ca 100644 --- a/app/actions/add_ons/uninstall_helm_chart.rb +++ b/app/actions/add_ons/uninstall_helm_chart.rb @@ -4,7 +4,7 @@ class AddOns::UninstallHelmChart executed do |context| add_on = context.add_on - client = K8::Helm::Client.new(add_on.cluster.kubeconfig, Cli::RunAndLog.new(add_on)) + client = K8::Helm::Client.connect(add_on.cluster.kubeconfig, Cli::RunAndLog.new(add_on)) charts = client.ls if charts.any? { |chart| chart['name'] == add_on.name } client.uninstall(add_on.name, namespace: add_on.name) diff --git a/app/controllers/add_ons_controller.rb b/app/controllers/add_ons_controller.rb index 34b0cccc..a274a6ab 100644 --- a/app/controllers/add_ons_controller.rb +++ b/app/controllers/add_ons_controller.rb @@ -78,6 +78,18 @@ class AddOnsController < ApplicationController redirect_to add_on_url(@add_on), notice: "Add on #{@add_on.name} restarted" end + def default_values + # Render a partial with the default values + @default_values = K8::Helm::Client + .new(Cli::RunAndReturnOutput.new) + .get_default_values_yaml( + repository_name: params[:repository_name], + repository_url: params[:repository_url], + chart_name: params[:chart_name] + ) + render partial: "add_ons/helm/default_values", locals: { default_values: @default_values } + end + # DELETE /add_ons/1 or /add_ons/1.json def destroy @add_on.uninstalling! diff --git a/app/javascript/controllers/helm_search_controller.js b/app/javascript/controllers/helm_search_controller.js index dc7f3b2b..4e57d71a 100644 --- a/app/javascript/controllers/helm_search_controller.js +++ b/app/javascript/controllers/helm_search_controller.js @@ -54,6 +54,7 @@ export default class extends Controller { // Add click handlers to all list items this.dropdown.querySelectorAll('li').forEach(li => { li.addEventListener('click', () => { + this.input.parentElement.classList.add('hidden') this.input.value = li.dataset.packageName const packageData = JSON.parse(li.dataset.packageData); this.hideDropdown() diff --git a/app/javascript/controllers/show_default_values_controller.js b/app/javascript/controllers/show_default_values_controller.js new file mode 100644 index 00000000..f62cbc11 --- /dev/null +++ b/app/javascript/controllers/show_default_values_controller.js @@ -0,0 +1,21 @@ +import { Controller } from "@hotwired/stimulus" +import { getDefaultValues } from "../utils/helm_charts" + +export default class extends Controller { + static targets = [ "modal", "content" ] + static values = { + repositoryName: String, + repositoryUrl: String, + chartName: String + } + connect() { + } + + async show(event) { + event.preventDefault() + const html = await getDefaultValues(this.repositoryNameValue, this.repositoryUrlValue, this.chartNameValue) + this.contentTarget.innerHTML = html + // Show the modal on the page already + this.modalTarget.showModal() + } +} diff --git a/app/javascript/utils/helm_charts/index.js b/app/javascript/utils/helm_charts/index.js index c5cb86eb..e4be6bec 100644 --- a/app/javascript/utils/helm_charts/index.js +++ b/app/javascript/utils/helm_charts/index.js @@ -1,8 +1,24 @@ +export async function getDefaultValues( + repositoryName, + repositoryUrl, + chartName, +) { + const params = new URLSearchParams({ + repository_name: repositoryName, + repository_url: repositoryUrl, + chart_name: chartName + }); + + const url = `/add_ons/default_values?${params.toString()}`; + const response = await fetch(url); + const html = await response.text() + return html; +} + export function renderHelmChartCard(packageData) { const logoImageId = packageData.logo_image_id; const logoImageUrl = `https://artifacthub.io/image/${logoImageId}`; - console.log(packageData) // Create a temporary container to convert HTML string to DOM element const tempContainer = document.createElement('div'); tempContainer.innerHTML = ` diff --git a/app/services/cli.rb b/app/services/cli.rb index 0e84d4ec..596da374 100644 --- a/app/services/cli.rb +++ b/app/services/cli.rb @@ -4,6 +4,7 @@ module Cli def call(command, envs: {}) command = envs.map { |k, v| "#{k}=#{v}" }.join(" ") + " #{command}" output = `#{command.strip}` + raise CommandFailedError, "Command `#{command}` failed with exit code #{$?.exitstatus}" unless $?.success? output end end diff --git a/app/services/k8/helm/client.rb b/app/services/k8/helm/client.rb index 8af95409..2e847e47 100644 --- a/app/services/k8/helm/client.rb +++ b/app/services/k8/helm/client.rb @@ -3,12 +3,26 @@ class K8::Helm::Client include K8::Kubeconfig attr_reader :kubeconfig, :runner - def initialize(kubeconfig, runner) - @kubeconfig = kubeconfig + def initialize(runner) @runner = runner end + def self.connect(kubeconfig, runner) + client = new(runner) + client.connect(kubeconfig) + end + + def connect(kubeconfig) + @kubeconfig = kubeconfig + self + end + + def connected? + @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| command = "helm get values #{name} --namespace #{namespace} --kubeconfig=#{kubeconfig_file.path}" output = runner.(command, envs: { "KUBECONFIG" => kubeconfig_file.path }) @@ -20,6 +34,7 @@ class K8::Helm::Client end def ls + return StandardError.new("Can't list helm charts if not connected") unless connected? with_kube_config do |kubeconfig_file| command_output = `helm ls --all-namespaces --kubeconfig=#{kubeconfig_file.path} -o yaml` output = YAML.safe_load(command_output) @@ -27,23 +42,23 @@ class K8::Helm::Client end def repo_update! - with_kube_config do |kubeconfig_file| - command = "helm repo update" - exit_status = runner.(command, envs: { "KUBECONFIG" => kubeconfig_file.path }) - raise "Helm repo update failed with exit status #{exit_status}" unless exit_status.success? - exit_status - end + exit_status = runner.("helm repo update") + raise "Helm repo update failed with exit status #{exit_status}" unless exit_status.success? + exit_status end - def add_repo(command) - with_kube_config do |kubeconfig_file| - exit_status = runner.(command, envs: { "KUBECONFIG" => kubeconfig_file.path }) - raise "Helm add repo failed with exit status #{exit_status}" unless exit_status.success? - exit_status - end + def run_command(command) + runner.(command) + end + + def add_repo(name, url) + add_repo_command = "helm repo add #{name} #{url}" + runner.(add_repo_command) end def install(name, chart_url, values: {}, namespace: 'default') + return StandardError.new("Can't install helm chart if not connected") unless connected? + with_kube_config do |kubeconfig_file| # Load the values.yaml file # Create a temporary file with the values.yaml content @@ -52,14 +67,17 @@ class K8::Helm::Client values_file.flush command = "helm upgrade --install #{name} #{chart_url} -f #{values_file.path} --namespace #{namespace}" + debugger exit_status = runner.(command, envs: { "KUBECONFIG" => kubeconfig_file.path }) - raise "Helm install failed with exit status #{exit_status}" unless exit_status.success? + raise "`#{command}` failed with exit status #{exit_status}" unless exit_status.success? exit_status end end end def uninstall(name, namespace: 'default') + return StandardError.new("Can't uninstall helm chart if not connected") unless connected? + with_kube_config do |kubeconfig_file| command = "helm uninstall #{name} --namespace #{namespace}" exit_status = runner.(command, envs: { "KUBECONFIG" => kubeconfig_file.path }) @@ -67,4 +85,15 @@ class K8::Helm::Client exit_status end end + + def get_default_values_yaml( + repository_name:, + repository_url:, + chart_name: + ) + add_repo(repository_name, repository_url) + command = "helm show values #{repository_name}/#{chart_name}" + output = runner.(command) + output + end end diff --git a/app/services/k8/helm/service.rb b/app/services/k8/helm/service.rb index 1785e41c..8fe2a182 100644 --- a/app/services/k8/helm/service.rb +++ b/app/services/k8/helm/service.rb @@ -24,8 +24,11 @@ class K8::Helm::Service end def values_yaml - helm_client = K8::Helm::Client.new(add_on.cluster.kubeconfig, Cli::RunAndReturnOutput.new) + helm_client = K8::Helm::Client.connect(add_on.cluster.kubeconfig, Cli::RunAndReturnOutput.new) helm_client.get_values_yaml(add_on.name, namespace: add_on.name) + rescue StandardError => e + Rails.logger.error("Error getting values.yaml for #{add_on.name}: #{e.message}") + nil end def storage_metrics @@ -52,7 +55,7 @@ class K8::Helm::Service end def version - services = K8::Helm::Client.new(add_on.cluster.kubeconfig, Cli::RunAndReturnOutput.new).ls + services = K8::Helm::Client.connect(add_on.cluster.kubeconfig, Cli::RunAndReturnOutput.new).ls chart = services.find { |service| service['name'] == add_on.name }['chart'] chart.match(/\d+\.\d+\.\d+/)&.to_s end diff --git a/app/views/add_ons/edit.html.erb b/app/views/add_ons/edit.html.erb index 694eaa7d..fb23cdb3 100644 --- a/app/views/add_ons/edit.html.erb +++ b/app/views/add_ons/edit.html.erb @@ -22,16 +22,13 @@ rows: 5, value: @service.values_yaml.to_yaml ) %> -
- Some settings like volume sizes and replica counts are not able to be edited after creation. -
<% end %>
- Changes updates to YAML values will update the chart, and could cause issues. + Changes to the values.yaml will be applied to the chart immediately.
diff --git a/app/views/add_ons/helm/_default_values.html.erb b/app/views/add_ons/helm/_default_values.html.erb new file mode 100644 index 00000000..81fa33ea --- /dev/null +++ b/app/views/add_ons/helm/_default_values.html.erb @@ -0,0 +1,3 @@ +
+<%= @default_values.gsub("\n", "
").html_safe %> +
diff --git a/app/views/add_ons/helm/_default_values_button.html.erb b/app/views/add_ons/helm/_default_values_button.html.erb new file mode 100644 index 00000000..54c874fe --- /dev/null +++ b/app/views/add_ons/helm/_default_values_button.html.erb @@ -0,0 +1,29 @@ +
+ + See default values.yml for this chart + + + + + + +
\ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 89733129..08a0a69b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -23,6 +23,7 @@ Rails.application.routes.draw do resources :add_ons do collection do get :search + get :default_values end member do post :restart