Skip to content

Commit

Permalink
feat: add command line arguments support
Browse files Browse the repository at this point in the history
  • Loading branch information
kairyou committed Nov 5, 2024
1 parent b0be85f commit eeab77c
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 112 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# jenkins-cli

A powerful and efficient Jenkins CLI tool written in Rust. Simplifies deployment of Jenkins jobs with an intuitive command-line experience.
A powerful and efficient Jenkins CLI tool written in Rust. Simplifies deployment of Jenkins projects through command line.

[中文文档](README_zh.md)

Expand Down Expand Up @@ -54,6 +54,21 @@ This command will:
3. Select a project and set build parameters
4. Trigger the build and show real-time console output

You can also use command line arguments:

```bash
# Run with Jenkins project URL - Deploy project directly without selection
jenkins -U http://jenkins.example.com:8081/job/My-Job/ -u username -t api_token

# Run with Jenkins server URL - Show project list for selection and deploy
jenkins -U http://jenkins.example.com:8081 -u username -t api_token
```

Available command line options:
- `-U, --url <URL>`: Jenkins server URL or project URL
- `-u, --user <USER>`: Jenkins username
- `-t, --token <TOKEN>`: Jenkins API token

## Configuration

Create a file named `.jenkins.toml` in your home directory with the following content:
Expand Down
25 changes: 20 additions & 5 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@
bash <(curl -fsSL https://raw.githubusercontent.com/kairyou/jenkins-cli/main/scripts/install.sh)
```

或者使用 ghp.ci 镜像(如果 GitHub 无法访问)
或使用 ghp.ci 镜像(如果无法访问 GitHub)

```bash
bash <(curl -fsSL https://ghp.ci/raw.githubusercontent.com/kairyou/jenkins-cli/main/scripts/install.sh)
```

如果已安装 Rust 和 Cargo,可以直接从 crates.io 安装 Jenkins CLI
如果已安装 Rust 和 Cargo,可以直接从 crates.io 安装:

```bash
cargo install jenkins
Expand All @@ -41,7 +41,7 @@ cargo install jenkins

## 使用

在设置好配置文件(见[配置](#configuration)部分)后,可以直接运行:
在设置好配置文件(见[配置](#配置)部分)后,可以直接运行:

```bash
jenkins
Expand All @@ -54,6 +54,21 @@ jenkins
3. 选择一个项目, 设置构建参数
4. 触发构建并实时输出控制台日志

或使用命令行参数:

```bash
# 使用 Jenkins 项目地址运行 - 无需选择直接发布指定项目
jenkins -U http://jenkins.example.com:8081/job/My-Job/ -u username -t api_token

# 使用 Jenkins 服务器地址运行 - 显示项目列表选择并发布
jenkins -U http://jenkins.example.com:8081 -u username -t api_token
```

可用的命令行选项:
- `-U, --url <URL>`: Jenkins 服务器地址或项目 URL
- `-u, --user <USER>`: Jenkins 用户名
- `-t, --token <TOKEN>`: Jenkins API 令牌

## 配置

`$HOME`目录下创建一个名为`.jenkins.toml`的文件,内容如下:
Expand Down Expand Up @@ -106,7 +121,7 @@ token = "your-api-token"

注意:正则表达式模式默认区分大小写,除非另有指定(例如,使用 `(?i)` 进行不区分大小写的匹配)。

### 用户名和 API 令牌
### 用户和 API 令牌

Jenkins User ID 就是登录 Jenkins 网页界面的用户名。

Expand All @@ -119,7 +134,7 @@ Jenkins User ID 就是登录 Jenkins 网页界面的用户名。
5. 为你的令牌命名,然后点击"生成"
6. 复制生成的令牌,并将其粘贴到你的`.jenkins.toml`文件中

注意:请妥善保管你的 API 令牌。不要分享或将其提交到版本控制系统中
注意:请妥善保管你的 API 令牌。不要分享或将其提交到版本控制系统��

## TODOs

Expand Down
2 changes: 2 additions & 0 deletions docs/dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ cargo add spinners # spinner

```bash
cargo run --
cargo run -- -U http://test.example.com:8081 -u <username> -t <token>
cargo run -- -U http://test.example.com:8081/job/CLI-Test-Job/ -u <username> -t <token>
# FORCE_UPDATE_CHECK=true cargo run --
```

Expand Down
3 changes: 2 additions & 1 deletion locales/en-US.ftl
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
select-project-prompt = Please select a project to deploy
select-project-failed = Failed to select project
get-project-failed = Failed to get project
get-projects-failed = Failed to get projects
get-job-parameters-failed = Failed to get job parameters
get-project-failed = Failed to get project info
prompt-input = Please enter {$name}
prompt-select = Please select {$name}
Expand Down
3 changes: 2 additions & 1 deletion locales/zh-CN.ftl
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
select-project-prompt = 请选择要发布的项目
select-project-failed = 选择项目失败
get-project-failed = 获取项目失败
get-projects-failed = 获取项目列表失败
get-job-parameters-failed = 获取任务参数失败
get-project-failed = 获取项目信息失败
prompt-input = 请输入{$name}
prompt-select = 请选择{$name}
Expand Down
85 changes: 69 additions & 16 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::i18n::I18n;
use crate::migrations::migrate_config_yaml_to_toml;
use crate::models::{Config, GlobalConfig, JenkinsConfig};

use crate::utils;
use crate::utils::clear_screen;

pub const CONFIG_FILE: &str = ".jenkins.toml";
Expand All @@ -39,7 +40,7 @@ pub static DATA_DIR: Lazy<PathBuf> = Lazy::new(|| {
data_dir
});

pub async fn initialize_config() -> Result<GlobalConfig> {
pub async fn initialize_config(matches: &clap::ArgMatches) -> Result<GlobalConfig> {
let _ = DATA_DIR.as_path(); // auto create data dir

let file_config = load_config().expect(&t!("load-config-failed"));
Expand All @@ -52,33 +53,86 @@ pub async fn initialize_config() -> Result<GlobalConfig> {

apply_global_settings(&global_config);

if jenkins_configs.is_empty()
|| jenkins_configs
.iter()
.any(|c| c.url.is_empty() || c.user.is_empty() || c.token.is_empty())
// println!("arg len: {}", std::env::args().len());
let url_arg = matches.get_one::<String>("url");
let cli_config = ["url", "user", "token"]
.iter()
.fold(JenkinsConfig::default(), |mut config, &field| {
if let Some(value) = matches.get_one::<String>(field) {
match field {
"url" => config.url = value.to_string(),
"user" => config.user = value.to_string(),
"token" => config.token = value.to_string(),
_ => {}
}
}
config
});

if url_arg.is_none()
&& (jenkins_configs.is_empty()
|| jenkins_configs
.iter()
.any(|c| c.url.is_empty() || c.user.is_empty() || c.token.is_empty()))
{
eprintln!("{}", t!("fill-required-config").yellow());
println!("{}", t!("jenkins-login-instruction"));
std::process::exit(1);
}

let mut config = CONFIG.lock().await;
config.global = Some(global_config.clone());
config.services = jenkins_configs;
let need_select = {
let mut config = CONFIG.lock().await;
config.global = Some(global_config.clone());
config.services = jenkins_configs;

match url_arg {
Some(url) => {
config.jenkins = Some(if config.services.is_empty() {
cli_config.clone()
} else {
let input_url = utils::simplify_url(url);
let matched_config = config
.services
.iter()
.find(|s| input_url == utils::simplify_url(&s.url))
.cloned();
match matched_config {
Some(matched) => JenkinsConfig {
user: if cli_config.user.is_empty() {
matched.user
} else {
cli_config.user
},
token: if cli_config.token.is_empty() {
matched.token
} else {
cli_config.token
},
..matched
},
None => cli_config,
}
});
false
}
None => !config.services.is_empty(),
}
};

if !config.services.is_empty() {
config.jenkins = Some(config.services[0].clone()); // default select first service
if need_select {
select_jenkins_service().await?;
}

Ok(global_config)
}

pub async fn select_jenkins_config() -> Result<()> {
pub async fn select_jenkins_service() -> Result<()> {
let mut config = CONFIG.lock().await;
let global_enable_history = config.global.as_ref().unwrap().enable_history.unwrap_or(true);
let services = config.services.clone();

let selected_config = if config.services.len() > 1 {
let service_names: Vec<String> = config.services.iter().map(|c| c.name.clone()).collect();
let selected_config = if services.len() > 1 {
let service_names: Vec<String> = services.iter().map(|c| c.name.clone()).collect();
let selection = FuzzySelect::with_theme(&ColorfulTheme::default())
.with_prompt(t!("select-jenkins"))
.items(&service_names)
Expand All @@ -95,13 +149,12 @@ pub async fn select_jenkins_config() -> Result<()> {
eprintln!("{}: {}", t!("select-jenkins-failed"), e);
std::process::exit(1);
});
config.services[selection].clone()
services[selection].clone()
} else {
config.services[0].clone()
services[0].clone()
};

let enable_history = selected_config.enable_history.unwrap_or(global_enable_history);

config.jenkins = Some(JenkinsConfig {
enable_history: Some(enable_history),
..selected_config
Expand Down
6 changes: 4 additions & 2 deletions src/jenkins/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::path::PathBuf;
use crate::config::DATA_DIR;
use crate::jenkins::ParamInfo;
use crate::migrations::{migrate_history, CURRENT_HISTORY_VERSION};
use crate::utils::current_timestamp;
use crate::utils::{self, current_timestamp};

pub const HISTORY_FILE: &str = "history.toml";

Expand All @@ -35,6 +35,7 @@ impl History {
/// Check if two history entries match
#[doc(hidden)]
fn matches_entry(entry: &HistoryEntry, info: &HistoryEntry) -> bool {
// println!("matches_entry: {:?}, {:?}", entry, info);
entry.job_url == info.job_url && entry.name == info.name
}

Expand Down Expand Up @@ -126,9 +127,10 @@ impl History {
#[doc(hidden)]
pub fn get_history(&self, info: &HistoryEntry, base_url: Option<&str>) -> Option<HistoryEntry> {
// self.entries.iter().find(|e| Self::matches_entry(e, info)).cloned()
let input_url = utils::simplify_url(base_url.unwrap_or(""));
self.entries
.iter()
.filter(|e| base_url.map_or(true, |url| e.job_url.contains(url)))
.filter(|e| input_url.is_empty() || e.job_url.contains(&input_url))
.find(|e| Self::matches_entry(e, info))
.cloned()
}
Expand Down
2 changes: 1 addition & 1 deletion src/jenkins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ fn default_param_type() -> ParamType {
ParamType::String
}

#[derive(Deserialize, Serialize, Debug)]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct JenkinsJob {
pub name: String,
#[serde(rename = "displayName")]
Expand Down
Loading

0 comments on commit eeab77c

Please sign in to comment.