Init commit
This commit is contained in:
45
src/config/extension.rs
Normal file
45
src/config/extension.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use log::info;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
pub(crate) struct Extension {
|
||||
name: Option<String>,
|
||||
target_dir: String,
|
||||
source_dir: String,
|
||||
}
|
||||
|
||||
impl Extension {
|
||||
pub(crate) fn apply(&self, chart_local_path: String) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let extension_name = match self.name.clone() {
|
||||
Some(res) => res,
|
||||
None => "Unnamed".to_string(),
|
||||
};
|
||||
info!("applying extension: '{}'", extension_name);
|
||||
let target_dir = format!("{}/{}", chart_local_path, self.target_dir);
|
||||
info!("trying to create a dir: {}", target_dir);
|
||||
fs::create_dir(target_dir.clone())?;
|
||||
info!("copying {} to {}", self.source_dir, target_dir);
|
||||
copy_recursively(self.source_dir.clone(), target_dir)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy files from source to destination recursively.
|
||||
pub fn copy_recursively(
|
||||
source: impl AsRef<Path>,
|
||||
destination: impl AsRef<Path>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
for entry in fs::read_dir(source)? {
|
||||
let entry = entry?;
|
||||
let filetype = entry.file_type()?;
|
||||
if filetype.is_dir() {
|
||||
copy_recursively(entry.path(), destination.as_ref().join(entry.file_name()))?;
|
||||
} else {
|
||||
info!("trying to copy {:?}", entry.path());
|
||||
fs::copy(entry.path(), destination.as_ref().join(entry.file_name()))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
184
src/config/mod.rs
Normal file
184
src/config/mod.rs
Normal file
@ -0,0 +1,184 @@
|
||||
use log::{info, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::File;
|
||||
|
||||
pub(crate) mod extension;
|
||||
pub(crate) mod patch;
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
pub(crate) struct Config {
|
||||
pub(crate) repositories: Vec<Repository>,
|
||||
pub(crate) charts: Vec<Chart>,
|
||||
pub(crate) mirrors: Vec<Mirror>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub(crate) fn new(config_path: String) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
info!("reading the config file");
|
||||
let config_content = File::open(config_path)?;
|
||||
let config: Config = serde_yaml::from_reader(config_content)?;
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum RepositoryKind {
|
||||
Helm,
|
||||
Git,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
pub(crate) struct Repository {
|
||||
// A name of the repository to be references by charts
|
||||
pub(crate) name: String,
|
||||
// Helm repository data
|
||||
pub(crate) helm: Option<Helm>,
|
||||
pub(crate) git: Option<Git>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
pub(crate) struct Mirror {
|
||||
pub(crate) name: String,
|
||||
pub(crate) git: Option<GitMirror>,
|
||||
pub(crate) custom_command: Option<CustomCommandsMirror>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
pub(crate) struct CustomCommandsMirror {
|
||||
pub(crate) package: Vec<String>,
|
||||
pub(crate) upload: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
pub(crate) struct GitMirror {
|
||||
pub(crate) url: String,
|
||||
pub(crate) path: Option<String>,
|
||||
pub(crate) branch: String,
|
||||
pub(crate) commit: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
pub(crate) struct Helm {
|
||||
// A url of the helm repository
|
||||
pub(crate) url: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
pub(crate) struct Git {
|
||||
pub(crate) url: String,
|
||||
#[serde(alias = "ref")]
|
||||
pub(crate) git_ref: String,
|
||||
pub(crate) path: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
pub(crate) struct Chart {
|
||||
// A name of the helm chart
|
||||
pub(crate) name: String,
|
||||
// A reference to repository by name
|
||||
pub(crate) repository: String,
|
||||
pub(crate) mirrors: Vec<String>,
|
||||
// Versions to be mirrored
|
||||
pub(crate) version: Option<String>,
|
||||
// A repository object
|
||||
pub(crate) extensions: Option<Vec<extension::Extension>>,
|
||||
pub(crate) patches: Option<Vec<patch::Patch>>,
|
||||
#[serde(skip_serializing)]
|
||||
pub(crate) repository_obj: Option<Repository>,
|
||||
#[serde(skip_serializing)]
|
||||
pub(crate) mirror_objs: Option<Vec<Mirror>>,
|
||||
}
|
||||
|
||||
impl Chart {
|
||||
pub(crate) fn populate_repository(
|
||||
&mut self,
|
||||
repositories: Vec<Repository>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
for repository in repositories {
|
||||
if repository.name == self.repository {
|
||||
self.repository_obj = Some(repository);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
//let err = error!("repo {} is not found in the repo list", self.repository);
|
||||
let error_msg = format!("repo {} is not found in the repo list", self.repository);
|
||||
return Err(Box::from(error_msg));
|
||||
}
|
||||
// TODO: Handle the "mirror not found" error
|
||||
pub(crate) fn populate_mirrors(
|
||||
&mut self,
|
||||
mirrors: Vec<Mirror>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut mirror_objs: Vec<Mirror> = vec![];
|
||||
|
||||
for mirror_global in mirrors.clone() {
|
||||
for mirror_name in self.mirrors.clone() {
|
||||
if mirror_name == mirror_global.name.clone() {
|
||||
mirror_objs.push(mirror_global.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
if mirror_objs.len() > 0 {
|
||||
self.mirror_objs = Some(mirror_objs);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn get_helm_repository_url(&self) -> String {
|
||||
match self.repository_obj.clone() {
|
||||
Some(res) => res.helm.unwrap().url,
|
||||
None => {
|
||||
warn!("repository object is not filled for chart {}", self.name);
|
||||
return "".to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_git_repository_url(&self) -> String {
|
||||
match self.repository_obj.clone() {
|
||||
Some(res) => res.git.unwrap().url,
|
||||
None => {
|
||||
warn!("repository object is not filled for chart {}", self.name);
|
||||
return "".to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_git_repository_ref(&self) -> String {
|
||||
match self.repository_obj.clone() {
|
||||
Some(res) => res.git.unwrap().git_ref,
|
||||
None => {
|
||||
warn!("repository object is not filled for chart {}", self.name);
|
||||
return "".to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_git_repository_path(&self) -> String {
|
||||
match self.repository_obj.clone() {
|
||||
Some(res) => res.git.unwrap().path,
|
||||
None => {
|
||||
warn!("repository object is not filled for chart {}", self.name);
|
||||
return "".to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_repo_kind(&self) -> Result<RepositoryKind, Box<dyn std::error::Error>> {
|
||||
match &self.repository_obj {
|
||||
Some(res) => {
|
||||
if res.helm.is_some() {
|
||||
return Ok(RepositoryKind::Helm);
|
||||
} else if res.git.is_some() {
|
||||
return Ok(RepositoryKind::Git);
|
||||
} else {
|
||||
return Err(Box::from("unknown repository kind is found"));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
return Err(Box::from(
|
||||
"repository object is not filled up for the chart",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
200
src/config/patch.rs
Normal file
200
src/config/patch.rs
Normal file
@ -0,0 +1,200 @@
|
||||
use std::{
|
||||
fs::{self, read_dir, remove_dir_all, File, OpenOptions},
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use log::{error, info};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::helpers::cli::{cli_exec, cli_exec_from_dir};
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
pub(crate) struct RegexpPatch {
|
||||
pub(crate) path: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
pub(crate) struct GitPatch {
|
||||
path: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
pub(crate) enum YqOperations {
|
||||
Add,
|
||||
Delete,
|
||||
Replace,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
pub(crate) struct YqPatch {
|
||||
file: String,
|
||||
op: YqOperations,
|
||||
key: String,
|
||||
value: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
pub(crate) struct CustomCommandPatch {
|
||||
commands: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
pub(crate) struct Patch {
|
||||
regexp: Option<RegexpPatch>,
|
||||
git: Option<GitPatch>,
|
||||
custom_command: Option<CustomCommandPatch>,
|
||||
yq: Option<YqPatch>,
|
||||
}
|
||||
|
||||
impl Patch {
|
||||
pub(crate) fn apply(&self, chart_local_path: String) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let patch_action = patch_action_from_definition(self.clone())?;
|
||||
patch_action.apply(chart_local_path)
|
||||
}
|
||||
}
|
||||
|
||||
trait PatchInterface {
|
||||
fn apply(&self, chart_local_path: String) -> Result<(), Box<dyn std::error::Error>>;
|
||||
}
|
||||
|
||||
impl PatchInterface for YqPatch {
|
||||
fn apply(&self, chart_local_path: String) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cmd = match self.op {
|
||||
YqOperations::Add => format!(
|
||||
"yq -i '{} += \"{}\"' {}",
|
||||
self.key,
|
||||
self.value.clone().unwrap(),
|
||||
self.file
|
||||
),
|
||||
YqOperations::Delete => format!("yq -i \'del({})\' {}", self.key, self.file),
|
||||
YqOperations::Replace => format!(
|
||||
"yq e -i \'{} = \"{}\"\' {}",
|
||||
self.key,
|
||||
self.value.clone().unwrap(),
|
||||
self.file
|
||||
),
|
||||
};
|
||||
cli_exec_from_dir(cmd, chart_local_path)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchInterface for RegexpPatch {
|
||||
fn apply(&self, chart_local_path: String) -> Result<(), Box<dyn std::error::Error>> {
|
||||
for entry in read_dir(self.path.clone())? {
|
||||
let entry = entry?;
|
||||
let filetype = entry.file_type()?;
|
||||
if filetype.is_dir() {
|
||||
error!(
|
||||
"reading dirs is not supported yet, skipping {:?}",
|
||||
entry.path()
|
||||
);
|
||||
} else {
|
||||
info!("reading a patch file: {:?}", entry.path());
|
||||
let config_content = File::open(entry.path())?;
|
||||
for patch_des in serde_yaml::Deserializer::from_reader(config_content) {
|
||||
let patch: crate::patch::regexp::RegexpPatch =
|
||||
match crate::patch::regexp::RegexpPatch::deserialize(patch_des) {
|
||||
Ok(patch) => patch,
|
||||
Err(err) => return Err(Box::from(err)),
|
||||
};
|
||||
info!("applying patch: {}", patch.name);
|
||||
let after = match patch.after {
|
||||
Some(after) => after,
|
||||
None => "".to_string(),
|
||||
};
|
||||
match patch.before {
|
||||
Some(before) => {
|
||||
let patch_regexp = regex::Regex::new(before.as_str())?;
|
||||
for target in patch.targets {
|
||||
let file_path = format!("{}/{}", chart_local_path, target);
|
||||
let file_content = fs::read_to_string(file_path.clone())?;
|
||||
let new_content =
|
||||
patch_regexp.replace_all(file_content.as_str(), after.clone());
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(file_path.clone())?;
|
||||
file.write(new_content.as_bytes())?;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
for target in patch.targets {
|
||||
let file_path = format!("{}/{}", chart_local_path, target);
|
||||
let file_content = fs::read_to_string(file_path.clone())?;
|
||||
let new_content = format!("{}\n{}", file_content, after);
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.append(false)
|
||||
.open(file_path.clone())?;
|
||||
file.write(new_content.as_bytes())?;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchInterface for GitPatch {
|
||||
fn apply(&self, chart_local_path: String) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if !is_git_repo(chart_local_path.clone()) {
|
||||
init_git_repo(chart_local_path.clone())?;
|
||||
};
|
||||
let cmd = format!("git -C {} apply {}", chart_local_path, self.path);
|
||||
cli_exec(cmd)?;
|
||||
remove_dir_all(chart_local_path + "/.git")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchInterface for CustomCommandPatch {
|
||||
fn apply(&self, chart_local_path: String) -> Result<(), Box<dyn std::error::Error>> {
|
||||
for cmd in self.commands.clone() {
|
||||
cli_exec_from_dir(cmd, chart_local_path.clone())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn patch_action_from_definition(
|
||||
patch: Patch,
|
||||
) -> Result<Box<dyn PatchInterface>, Box<dyn std::error::Error>> {
|
||||
if let Some(regexp) = patch.regexp {
|
||||
return Ok(Box::new(RegexpPatch { path: regexp.path }));
|
||||
} else if let Some(git) = patch.git {
|
||||
return Ok(Box::new(GitPatch {
|
||||
path: {
|
||||
let path = PathBuf::from(git.path);
|
||||
let can_path = fs::canonicalize(&path).ok().unwrap();
|
||||
can_path.into_os_string().into_string().ok().unwrap()
|
||||
},
|
||||
}));
|
||||
} else if let Some(custom_command) = patch.custom_command {
|
||||
return Ok(Box::new(CustomCommandPatch {
|
||||
commands: custom_command.commands,
|
||||
}));
|
||||
} else if let Some(yq) = patch.yq {
|
||||
if yq.op != YqOperations::Delete && yq.value.is_none() {
|
||||
return Err(Box::from("yq patch of non kind 'delete' requires a value"));
|
||||
};
|
||||
return Ok(Box::from(yq));
|
||||
} else {
|
||||
return Err(Box::from("unknown patch type"));
|
||||
};
|
||||
}
|
||||
|
||||
fn is_git_repo(path: String) -> bool {
|
||||
let dot_git_path = path + ".git";
|
||||
Path::new(dot_git_path.as_str()).exists()
|
||||
}
|
||||
|
||||
pub(crate) fn init_git_repo(path: String) -> Result<(), Box<dyn std::error::Error>> {
|
||||
cli_exec(format!("git -C {} init .", path))?;
|
||||
cli_exec(format!("git -C {} add .", path))?;
|
||||
cli_exec(format!("git -C {} commit -m 'Init commit'", path))?;
|
||||
Ok(())
|
||||
}
|
Reference in New Issue
Block a user