Skip to content

Commit

Permalink
Reorganize structs in different files
Browse files Browse the repository at this point in the history
  • Loading branch information
Andre Claudino authored and nicholastmosher committed Jun 18, 2021
1 parent 7346841 commit e362e11
Show file tree
Hide file tree
Showing 7 changed files with 433 additions and 414 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ RUSTV=stable

test-all:
cargo build --all-features
cargo test --all
cargo test --all

install-fmt:
rustup component add rustfmt --toolchain $(RUSTV)
Expand Down
19 changes: 19 additions & 0 deletions src/chart.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use serde::Deserialize;

/// A representation of a chart definition in a repo.
#[derive(Debug, Deserialize)]
pub struct Chart {
/// The chart name
name: String,
/// The chart version
version: String,
}

impl Chart {
pub fn version(&self) -> &str {
&self.version
}
pub fn name(&self) -> &str {
&self.name
}
}
181 changes: 181 additions & 0 deletions src/helm_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
use fluvio_command::CommandExt;
use std::process::Command;
use tracing::{instrument, warn};

use super::Chart;
use super::HelmError;
use super::InstallArg;
use super::InstalledChart;
use super::UninstallArg;

/// Client to manage helm operations
#[derive(Debug)]
#[non_exhaustive]
pub struct HelmClient {}

impl HelmClient {
/// Creates a Rust client to manage our helm needs.
///
/// This only succeeds if the helm command can be found.
pub fn new() -> Result<Self, HelmError> {
let output = Command::new("helm").arg("version").result()?;

// Convert command output into a string
let out_str = String::from_utf8(output.stdout).map_err(HelmError::Utf8Error)?;

// Check that the version command gives a version.
// In the future, we can parse the version string and check
// for compatible CLI client version.
if !out_str.contains("version") {
return Err(HelmError::HelmVersionNotFound(out_str));
}

// If checks succeed, create Helm client
Ok(Self {})
}

/// Installs the given chart under the given name.
///
#[instrument(skip(self))]
pub fn install(&self, args: &InstallArg) -> Result<(), HelmError> {
let mut command = args.install();
command.result()?;
Ok(())
}

/// Upgrades the given chart
#[instrument(skip(self))]
pub fn upgrade(&self, args: &InstallArg) -> Result<(), HelmError> {
let mut command = args.upgrade();
command.result()?;
Ok(())
}

/// Uninstalls specified chart library
pub fn uninstall(&self, uninstall: UninstallArg) -> Result<(), HelmError> {
if uninstall.ignore_not_found {
let app_charts = self
.get_installed_chart_by_name(&uninstall.release, uninstall.namespace.as_deref())?;
if app_charts.is_empty() {
warn!("Chart does not exists, {}", &uninstall.release);
return Ok(());
}
}
let mut command: Command = uninstall.into();
command.result()?;
Ok(())
}

/// Adds a new helm repo with the given chart name and chart location
#[instrument(skip(self))]
pub fn repo_add(&self, chart: &str, location: &str) -> Result<(), HelmError> {
Command::new("helm")
.args(&["repo", "add", chart, location])
.result()?;
Ok(())
}

/// Updates the local helm repository
#[instrument(skip(self))]
pub fn repo_update(&self) -> Result<(), HelmError> {
Command::new("helm").args(&["repo", "update"]).result()?;
Ok(())
}

/// Searches the repo for the named helm chart
#[instrument(skip(self))]
pub fn search_repo(&self, chart: &str, version: &str) -> Result<Vec<Chart>, HelmError> {
let mut command = Command::new("helm");
command
.args(&["search", "repo", chart])
.args(&["--version", version])
.args(&["--output", "json"]);

let output = command.result()?;

check_helm_stderr(output.stderr)?;
serde_json::from_slice(&output.stdout).map_err(HelmError::Serde)
}

/// Get all the available versions
#[instrument(skip(self))]
pub fn versions(&self, chart: &str) -> Result<Vec<Chart>, HelmError> {
let mut command = Command::new("helm");
command
.args(&["search", "repo"])
.args(&["--versions", chart])
.args(&["--output", "json", "--devel"]);
let output = command.result()?;

check_helm_stderr(output.stderr)?;
serde_json::from_slice(&output.stdout).map_err(HelmError::Serde)
}

/// Checks that a given version of a given chart exists in the repo.
#[instrument(skip(self))]
pub fn chart_version_exists(&self, name: &str, version: &str) -> Result<bool, HelmError> {
let versions = self.search_repo(name, version)?;
let count = versions
.iter()
.filter(|chart| chart.name() == name && chart.version() == version)
.count();
Ok(count > 0)
}

/// Returns the list of installed charts by name
#[instrument(skip(self))]
pub fn get_installed_chart_by_name(
&self,
name: &str,
namespace: Option<&str>,
) -> Result<Vec<InstalledChart>, HelmError> {
let exact_match = format!("^{}$", name);
let mut command = Command::new("helm");
command
.arg("list")
.arg("--filter")
.arg(exact_match)
.arg("--output")
.arg("json");

match namespace {
Some(ns) => {
command.args(&["--namespace", ns]);
}
None => {
// Search all namespaces
command.args(&["-A"]);
}
}

let output = command.result()?;
check_helm_stderr(output.stderr)?;
serde_json::from_slice(&output.stdout).map_err(HelmError::Serde)
}

/// get helm package version
#[instrument(skip(self))]
pub fn get_helm_version(&self) -> Result<String, HelmError> {
let helm_version = Command::new("helm")
.arg("version")
.arg("--short")
.output()
.map_err(HelmError::HelmNotInstalled)?;
let version_text = String::from_utf8(helm_version.stdout).map_err(HelmError::Utf8Error)?;
Ok(version_text[1..].trim().to_string())
}
}

/// Check for errors in Helm's stderr output
///
/// Returns `Ok(())` if everything is fine, or `HelmError` if something is wrong
fn check_helm_stderr(stderr: Vec<u8>) -> Result<(), HelmError> {
if !stderr.is_empty() {
let stderr = String::from_utf8(stderr)?;
if stderr.contains("Kubernetes cluster unreachable") {
return Err(HelmError::FailedToConnect);
}
}

Ok(())
}
135 changes: 135 additions & 0 deletions src/install_arg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use std::path::PathBuf;
use std::process::Command;

/// Installer Argument
#[derive(Debug)]
pub struct InstallArg {
pub name: String,
pub chart: String,
pub version: Option<String>,
pub namespace: Option<String>,
pub opts: Vec<(String, String)>,
pub values: Vec<PathBuf>,
pub develop: bool,
}

impl InstallArg {
pub fn new<N: Into<String>, C: Into<String>>(name: N, chart: C) -> Self {
Self {
name: name.into(),
chart: chart.into(),
version: None,
namespace: None,
opts: vec![],
values: vec![],
develop: false,
}
}

/// set chart version
pub fn version<S: Into<String>>(mut self, version: S) -> Self {
self.version = Some(version.into());
self
}

/// set namepsace
pub fn namespace<S: Into<String>>(mut self, ns: S) -> Self {
self.namespace = Some(ns.into());
self
}

/// reset array of options
pub fn opts(mut self, options: Vec<(String, String)>) -> Self {
self.opts = options;
self
}

/// set a single option
pub fn opt<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
self.opts.push((key.into(), value.into()));
self
}

/// set to use develop
pub fn develop(mut self) -> Self {
self.develop = true;
self
}

/// set list of values
pub fn values(mut self, values: Vec<PathBuf>) -> Self {
self.values = values;
self
}

/// set one value
pub fn value(&mut self, value: PathBuf) -> &mut Self {
self.values.push(value);
self
}

pub fn install(&self) -> Command {
let mut command = Command::new("helm");
command.args(&["install", &self.name, &self.chart]);
self.apply_args(&mut command);
command
}

pub fn upgrade(&self) -> Command {
let mut command = Command::new("helm");
command.args(&["upgrade", "--install", &self.name, &self.chart]);
self.apply_args(&mut command);
command
}

fn apply_args(&self, command: &mut Command) {
if let Some(namespace) = &self.namespace {
command.args(&["--namespace", namespace]);
}

if self.develop {
command.arg("--devel");
}

if let Some(version) = &self.version {
command.args(&["--version", version]);
}

for value_path in &self.values {
command.arg("--values").arg(value_path);
}

for (key, val) in &self.opts {
command.arg("--set").arg(format!("{}={}", key, val));
}
}
}

impl From<InstallArg> for Command {
fn from(arg: InstallArg) -> Self {
let mut command = Command::new("helm");
command.args(&["install", &arg.name, &arg.chart]);

if let Some(namespace) = &arg.namespace {
command.args(&["--namespace", namespace]);
}

if arg.develop {
command.arg("--devel");
}

if let Some(version) = &arg.version {
command.args(&["--version", version]);
}

for value_path in &arg.values {
command.arg("--values").arg(value_path);
}

for (key, val) in &arg.opts {
command.arg("--set").arg(format!("{}={}", key, val));
}

command
}
}
18 changes: 18 additions & 0 deletions src/installed_chart.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use serde::Deserialize;

/// A representation of an installed chart.
#[derive(Debug, Deserialize)]
pub struct InstalledChart {
/// The chart name
pub name: String,
/// The version of the app this chart installed
pub app_version: String,
/// The chart revision
pub revision: String,
/// Date/time when the chart was last updated
pub updated: String,
/// Status of the installed chart
pub status: String,
/// The ID of the chart that is installed
pub chart: String,
}
Loading

0 comments on commit e362e11

Please sign in to comment.