Skip to content
This repository has been archived by the owner on Dec 21, 2021. It is now read-only.

Set KUBECONFIG in systemd services #234

Merged
merged 2 commits into from
Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

:224: https://github.com/stackabletech/agent/pull/224[#224]
:229: https://github.com/stackabletech/agent/pull/229[#229]
:234: https://github.com/stackabletech/agent/pull/234[#234]

=== Added
* `hostIP` and `podIP` added to the pod status ({224}).
* Environment variable `KUBECONFIG` set in systemd services ({234}).

=== Fixed
* Invalid or unreachable repositories are skipped when searching for
Expand Down
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ version = "0.5.0-nightly"
anyhow = "1.0"
async-trait = "0.1"
byteorder = "1.4"
dirs = "3.0"
env_logger = "0.9"
flate2 = "1.0"
futures-util = "0.3"
Expand All @@ -25,6 +26,7 @@ kubelet = { git = "https://github.com/stackabletech/krustlet.git", branch = "sta
Inflector = "0.11"
lazy_static = "1.4"
log = "0.4"
multimap = "0.8"
nix = "0.22"
oci-distribution = { git = "https://github.com/stackabletech/krustlet.git", branch = "stackable_patches_v0.7.0" } # version = "0.6"
regex = "1.4"
Expand Down
40 changes: 38 additions & 2 deletions src/provider/states/pod/creating_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ use kubelet::{
container::ContainerKey,
pod::{Pod, PodKey},
};
use log::{debug, error, info};
use log::{debug, error, info, warn};

use super::setup_failed::SetupFailed;
use super::starting::Starting;
use crate::provider::systemdmanager::systemdunit::SystemDUnit;
use crate::provider::{ContainerHandle, PodState, ProviderState};
use anyhow::Error;
use dirs::home_dir;
use std::env;
use std::fs::create_dir_all;
use std::path::PathBuf;

#[derive(Default, Debug, TransitionTo)]
#[transition_to(Starting, SetupFailed)]
Expand Down Expand Up @@ -72,7 +75,7 @@ impl State<PodState> for CreatingService {
// systemd unit file/service.
// Map every container from the pod object to a systemdunit
for container in &pod.containers() {
let unit = match SystemDUnit::new(
let mut unit = match SystemDUnit::new(
&unit_template,
&service_prefix,
&container,
Expand All @@ -83,6 +86,30 @@ impl State<PodState> for CreatingService {
Err(err) => return Transition::Complete(Err(Error::from(err))),
};

if let Some(kubeconfig_path) = find_kubeconfig() {
const UNIT_ENV_KEY: &str = "KUBECONFIG";
if let Some(kubeconfig_path) = kubeconfig_path.to_str() {
unit.add_env_var(UNIT_ENV_KEY, kubeconfig_path);
} else {
warn!(
"Environment variable {} cannot be added to \
the systemd service [{}] because the path [{}] \
is not valid unicode.",
UNIT_ENV_KEY,
service_name,
kubeconfig_path.to_string_lossy()
);
}
} else {
warn!(
"Kubeconfig file not found. It will not be added \
to the environment variables of the systemd \
service [{}]. If no kubeconfig is present then the \
Stackable agent should have generated one.",
service_name
);
}

// Create the service
// As per ADR005 we currently write the unit files directly in the systemd
// unit directory (by passing None as [unit_file_path]).
Expand Down Expand Up @@ -121,3 +148,12 @@ impl State<PodState> for CreatingService {
Ok(make_status(Phase::Pending, &"CreatingService"))
}
}

/// Tries to find the kubeconfig file in the environment variable
/// `KUBECONFIG` and on the path `$HOME/.kube/config`
fn find_kubeconfig() -> Option<PathBuf> {
let env_var = env::var_os("KUBECONFIG").map(PathBuf::from);
let default_path = || home_dir().map(|home| home.join(".kube").join("config"));

env_var.or_else(default_path).filter(|path| path.exists())
}
65 changes: 41 additions & 24 deletions src/provider/systemdmanager/systemdunit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ use crate::provider::states::pod::PodState;
use crate::provider::systemdmanager::manager::UnitTypes;
use lazy_static::lazy_static;
use log::{debug, error, info, trace, warn};
use multimap::MultiMap;
use regex::Regex;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::iter;
use std::iter::{self, repeat};
use strum::{Display, EnumIter, IntoEnumIterator};

/// The default timeout for stopping a service, after this has passed systemd will terminate
Expand Down Expand Up @@ -43,7 +44,7 @@ lazy_static! {
pub struct SystemDUnit {
pub name: String,
pub unit_type: UnitTypes,
pub sections: HashMap<Section, HashMap<String, String>>,
pub sections: HashMap<Section, MultiMap<String, String>>,
}

// TODO: The parsing code is also highly stackable specific, we should
Expand Down Expand Up @@ -106,41 +107,35 @@ impl SystemDUnit {

unit.name = format!("{}{}", name_prefix, trimmed_name);

unit.add_property(Section::Unit, "Description", &unit.name.clone());
unit.set_property(Section::Unit, "Description", &unit.name.clone());

unit.add_property(
unit.set_property(
Section::Service,
"ExecStart",
&SystemDUnit::get_command(container, template_data, package_root)?,
);

let env_vars = SystemDUnit::get_environment(container, service_name, template_data)?;
if !env_vars.is_empty() {
let mut assignments = env_vars
.iter()
.map(|(k, v)| format!("\"{}={}\"", k, v))
.collect::<Vec<_>>();
assignments.sort();
// TODO Put every environment variable on a separate line
unit.add_property(Section::Service, "Environment", &assignments.join(" "));
for (key, value) in env_vars {
unit.add_env_var(&key, &value);
}

// These are currently hard-coded, as this is not something we expect to change soon
unit.add_property(Section::Service, "StandardOutput", "journal");
unit.add_property(Section::Service, "StandardError", "journal");
unit.set_property(Section::Service, "StandardOutput", "journal");
unit.set_property(Section::Service, "StandardError", "journal");

if let Some(user_name) =
SystemDUnit::get_user_name_from_security_context(container, &unit.name)?
{
if !user_mode {
unit.add_property(Section::Service, "User", user_name);
unit.set_property(Section::Service, "User", user_name);
} else {
info!("The user name [{}] in spec.containers[name = {}].securityContext.windowsOptions.runAsUserName is not set in the systemd unit because the agent runs in session mode.", user_name, container.name());
}
}

// This one is mandatory, as otherwise enabling the unit fails
unit.add_property(Section::Install, "WantedBy", "multi-user.target");
unit.set_property(Section::Install, "WantedBy", "multi-user.target");

Ok(unit)
}
Expand Down Expand Up @@ -208,10 +203,10 @@ impl SystemDUnit {
}
.to_string();

unit.add_property(Section::Service, "TimeoutStopSec", &termination_timeout);
unit.set_property(Section::Service, "TimeoutStopSec", &termination_timeout);

if let Some(stop_timeout) = pod_spec.termination_grace_period_seconds {
unit.add_property(
unit.set_property(
Section::Service,
"TimeoutStopSec",
stop_timeout.to_string().as_str(),
Expand All @@ -220,7 +215,7 @@ impl SystemDUnit {

if let Some(user_name) = SystemDUnit::get_user_name_from_pod_security_context(pod)? {
if !user_mode {
unit.add_property(Section::Service, "User", user_name);
unit.set_property(Section::Service, "User", user_name);
} else {
info!("The user name [{}] in spec.securityContext.windowsOptions.runAsUserName is not set in the systemd unit because the agent runs in session mode.", user_name);
}
Expand Down Expand Up @@ -262,9 +257,29 @@ impl SystemDUnit {
format!("{}.{}", self.name, lower_type)
}

/// Add a key=value entry to the specified section
/// Adds an environment variable to the service section of the unit file
pub fn add_env_var(&mut self, key: &str, value: &str) {
self.add_property(
Section::Service,
"Environment",
&format!("\"{}={}\"", key, value),
);
}

/// Sets a property in the given section
///
/// If properties with the given key already exist then they are
/// replaced with the given one.
fn set_property(&mut self, section: Section, key: &str, value: &str) {
let section = self.sections.entry(section).or_default();
*section.entry(String::from(key)).or_insert_vec(Vec::new()) = vec![String::from(value)];
}

/// Adds a property to the given section
///
/// Properties with the same key remain untouched.
fn add_property(&mut self, section: Section, key: &str, value: &str) {
let section = self.sections.entry(section).or_insert_with(HashMap::new);
let section = self.sections.entry(section).or_default();
section.insert(String::from(key), String::from(value));
}

Expand All @@ -278,11 +293,12 @@ impl SystemDUnit {
.join("\n\n")
}

fn write_section(section: &Section, entries: &HashMap<String, String>) -> String {
fn write_section(section: &Section, entries: &MultiMap<String, String>) -> String {
let header = format!("[{}]", section);

let mut body = entries
.iter()
.iter_all()
.flat_map(|(key, values)| repeat(key).zip(values))
.map(|(key, value)| format!("{}={}", key, value))
.collect::<Vec<_>>();
body.sort();
Expand Down Expand Up @@ -523,7 +539,8 @@ mod test {
Description=default-stackable-test-container

[Service]
Environment="LOG_DIR=/var/log/default-stackable" "LOG_LEVEL=INFO"
Environment="LOG_DIR=/var/log/default-stackable"
Environment="LOG_LEVEL=INFO"
ExecStart=start.sh arg /etc/default-stackable
StandardError=journal
StandardOutput=journal
Expand Down