Skip to content

Commit bdc9b0d

Browse files
authored
Merge pull request #76 from ClickHouse/issue-74-download-sources
Use builds.clickhouse.com as primary download source
2 parents 441bdc8 + 54da26d commit bdc9b0d

11 files changed

Lines changed: 1301 additions & 246 deletions

File tree

.github/workflows/test-install.yml

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
name: Test Install
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- "src/version_manager/**"
7+
- "src/main.rs"
8+
- "src/cli.rs"
9+
- "Cargo.toml"
10+
- ".github/workflows/test-install.yml"
11+
12+
jobs:
13+
unit-tests:
14+
name: Unit Tests
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
- uses: dtolnay/rust-toolchain@stable
19+
- run: cargo test
20+
21+
url-probing:
22+
name: URL Probing (${{ matrix.platform }})
23+
runs-on: ubuntu-latest
24+
strategy:
25+
matrix:
26+
include:
27+
- platform: amd64
28+
desc: Linux x86_64
29+
- platform: aarch64
30+
desc: Linux ARM64
31+
- platform: macos
32+
desc: macOS x86_64
33+
- platform: macos-aarch64
34+
desc: macOS ARM64
35+
steps:
36+
- name: Verify builds.clickhouse.com URLs
37+
run: |
38+
echo "Testing builds.clickhouse.com for ${{ matrix.desc }}..."
39+
40+
# master should always be available
41+
echo -n " master: "
42+
curl -sf -o /dev/null -w "%{http_code}" \
43+
"https://builds.clickhouse.com/master/${{ matrix.platform }}/clickhouse"
44+
echo ""
45+
46+
# A known recent version should be available
47+
echo -n " 25.12: "
48+
curl -sf -o /dev/null -w "%{http_code}" \
49+
"https://builds.clickhouse.com/25.12/${{ matrix.platform }}/clickhouse"
50+
echo ""
51+
52+
# A version too old should return 403
53+
echo -n " 24.1 (expect 403): "
54+
code=$(curl -s -o /dev/null -w "%{http_code}" \
55+
"https://builds.clickhouse.com/24.1/${{ matrix.platform }}/clickhouse")
56+
echo "$code"
57+
if [ "$code" != "403" ]; then
58+
echo "Expected 403 for old version, got $code"
59+
exit 1
60+
fi
61+
62+
- name: Verify packages.clickhouse.com URLs (Linux only)
63+
if: matrix.platform == 'amd64' || matrix.platform == 'aarch64'
64+
run: |
65+
arch=${{ matrix.platform }}
66+
# packages uses arm64 not aarch64
67+
if [ "$arch" = "aarch64" ]; then arch="arm64"; fi
68+
69+
echo "Testing packages.clickhouse.com for ${{ matrix.desc }}..."
70+
71+
# Check a known stable version exists
72+
echo -n " stable tarball: "
73+
curl -sf -o /dev/null -w "%{http_code}" -I \
74+
"https://packages.clickhouse.com/tgz/stable/clickhouse-common-static-25.3.2.39-${arch}.tgz"
75+
echo ""
76+
77+
# Check client-only tarball exists
78+
echo -n " client-only tarball: "
79+
curl -sf -o /dev/null -w "%{http_code}" -I \
80+
"https://packages.clickhouse.com/tgz/stable/clickhouse-client-25.3.2.39-${arch}.tgz"
81+
echo ""
82+
83+
install-test:
84+
name: Install Test (${{ matrix.os }})
85+
runs-on: ${{ matrix.os }}
86+
strategy:
87+
fail-fast: false
88+
matrix:
89+
os: [ubuntu-latest, macos-latest]
90+
steps:
91+
- uses: actions/checkout@v4
92+
93+
- uses: dtolnay/rust-toolchain@stable
94+
95+
- name: Build CLI
96+
run: cargo build
97+
98+
- name: Test install latest
99+
run: |
100+
./target/debug/clickhousectl local install latest
101+
# Verify a version was installed
102+
./target/debug/clickhousectl local list | grep -E "^ [0-9]"
103+
104+
- name: Test install minor version
105+
run: |
106+
./target/debug/clickhousectl local install 25.12
107+
./target/debug/clickhousectl local list | grep "25.12"
108+
109+
- name: Test local use
110+
run: |
111+
./target/debug/clickhousectl local use 25.12
112+
./target/debug/clickhousectl local which | grep "25.12"
113+
114+
- name: Test already installed (skips download)
115+
run: |
116+
./target/debug/clickhousectl local install 25.12 2>&1 | grep "already installed"
117+
118+
- name: Test force re-install
119+
run: |
120+
./target/debug/clickhousectl local install 25.12 --force
121+
122+
- name: Test invalid version (expect error)
123+
run: |
124+
if ./target/debug/clickhousectl local install foo 2>&1; then
125+
echo "Should have errored for invalid version"
126+
exit 1
127+
fi
128+
129+
- name: Test 3-part version rejected (expect error)
130+
run: |
131+
if ./target/debug/clickhousectl local install 25.12.9 2>&1; then
132+
echo "Should have errored for 3-part version"
133+
exit 1
134+
fi
135+
136+
- name: Verify installed binary works
137+
run: |
138+
version_dir=$(ls -d ~/.clickhouse/versions/*/clickhouse | head -1)
139+
$version_dir --version

src/cli.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,21 +114,21 @@ pub enum LocalCommands {
114114
/// Install a ClickHouse version
115115
#[command(after_help = "\
116116
CONTEXT FOR AGENTS:
117-
Downloads a ClickHouse binary to ~/.clickhouse/versions/{version}/.
118-
Accepts version specs: \"stable\", \"lts\", partial like \"25.12\", or exact like \"25.12.5.44\".
119-
Optionally set as default with `clickhousectl local use <version>`.
120-
`clickhousectl local use <version>` will auto-install if the version is missing and set as default.
121-
Related: `clickhousectl local list --remote` to see downloadable versions.")]
117+
`clickhousectl local use <version>` will auto-install if the version is missing and set as default.")]
122118
Install {
123-
/// Version to install (e.g., 25.1.2.3, 25.1, stable, lts)
119+
/// Version to install (e.g., latest, stable, lts, 25, 25.12, 25.12.9.61)
124120
version: String,
121+
122+
/// Force re-install even if version is already installed
123+
#[arg(long)]
124+
force: bool,
125125
},
126126

127127
/// List installed versions
128128
#[command(after_help = "\
129129
CONTEXT FOR AGENTS:
130130
Without flags: shows locally installed versions (exact version strings).
131-
With --remote: shows versions available for download from GitHub releases.
131+
With --remote: shows versions available for download from builds.clickhouse.com.
132132
Use the exact version strings from this output with `clickhousectl local remove` or `clickhousectl local use`.
133133
Related: `clickhousectl local install <version>` to install, `clickhousectl local which` to see current default.")]
134134
List {

src/cloud/commands.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,22 +1713,25 @@ async fn ensure_version_installed(
17131713

17141714
let version_spec = service_version.ok_or("service has no clickhouse_version set")?;
17151715

1716-
// Resolve the spec to an exact version first (e.g. "25.12" -> "25.12.9.61")
1717-
let resolved = version_manager::resolve_version(version_spec).await?;
1716+
let spec = version_manager::parse_version_spec(version_spec)?;
1717+
let platform = version_manager::platform::Platform::detect()?;
1718+
let resolved = version_manager::resolve::resolve(&spec, &platform).await?;
17181719

1719-
// Check if already installed
1720-
let version_dir = paths::version_dir(&resolved.version)?;
1721-
if version_dir.exists() {
1722-
return Ok(resolved.version);
1720+
// If exact version is known, check if already installed
1721+
if let Some(ref version) = resolved.exact_version {
1722+
let version_dir = paths::version_dir(version)?;
1723+
if version_dir.exists() {
1724+
return Ok(version.clone());
1725+
}
17231726
}
17241727

1725-
// Install
17261728
eprintln!(
17271729
"Service requires ClickHouse {} — downloading...",
1728-
resolved.version
1730+
resolved.display_version
17291731
);
1730-
version_manager::install_version(&resolved.version, resolved.channel).await?;
1731-
Ok(resolved.version)
1732+
let version =
1733+
version_manager::install::install_resolved(&resolved, &platform, false).await?;
1734+
Ok(version)
17321735
}
17331736

17341737
#[cfg(test)]

src/main.rs

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ async fn run_skills(args: SkillsArgs) -> Result<()> {
4646

4747
async fn run_local(cmd: LocalCommands) -> Result<()> {
4848
match cmd {
49-
LocalCommands::Install { version } => install(&version).await,
49+
LocalCommands::Install { version, force } => install(&version, force).await,
5050
LocalCommands::List { remote } => {
5151
if remote {
5252
list_available().await
@@ -73,12 +73,21 @@ async fn run_local(cmd: LocalCommands) -> Result<()> {
7373
}
7474
}
7575

76-
async fn install(version_spec: &str) -> Result<()> {
77-
println!("Resolving version {}...", version_spec);
78-
let entry = version_manager::resolve_version(version_spec).await?;
79-
println!("Resolved to version {} ({})", entry.version, entry.channel);
76+
async fn install(version_spec: &str, force: bool) -> Result<()> {
77+
let spec = version_manager::parse_version_spec(version_spec)?;
78+
let platform = version_manager::platform::Platform::detect()?;
79+
80+
eprintln!("Resolving {}...", spec);
81+
let resolved = version_manager::resolve::resolve(&spec, &platform).await?;
82+
83+
let version = version_manager::install::install_resolved(&resolved, &platform, force).await?;
84+
85+
// If this is the first installed version, set it as default
86+
if version_manager::get_default_version().is_err() {
87+
version_manager::set_default_version(&version)?;
88+
eprintln!("Set as default version");
89+
}
8090

81-
version_manager::install_version(&entry.version, entry.channel).await?;
8291
Ok(())
8392
}
8493

@@ -105,8 +114,8 @@ fn list_installed() -> Result<()> {
105114
}
106115

107116
async fn list_available() -> Result<()> {
108-
println!("Fetching available versions...");
109-
let versions = version_manager::list_available_versions().await?;
117+
eprintln!("Checking available versions on builds.clickhouse.com...");
118+
let versions = version_manager::list_available_versions_from_builds().await?;
110119

111120
if versions.is_empty() {
112121
println!("No versions available");
@@ -115,36 +124,47 @@ async fn list_available() -> Result<()> {
115124

116125
let installed = version_manager::list_installed_versions().unwrap_or_default();
117126

118-
println!("Available versions:");
119-
for entry in versions.iter().take(20) {
120-
if installed.contains(&entry.version) {
121-
println!(" {} [{}] (installed)", entry.version, entry.channel);
127+
println!("Available versions (builds.clickhouse.com):");
128+
for v in &versions {
129+
// Check if any installed version matches this minor
130+
let prefix = format!("{}.", v);
131+
let is_installed = installed.iter().any(|iv| iv.starts_with(&prefix) || iv == v);
132+
if is_installed {
133+
println!(" {} (installed)", v);
122134
} else {
123-
println!(" {} [{}]", entry.version, entry.channel);
135+
println!(" {}", v);
124136
}
125137
}
126-
127-
if versions.len() > 20 {
128-
println!(" ... and {} more", versions.len() - 20);
129-
}
138+
println!();
139+
println!("Install with: clickhousectl local install <version>");
140+
println!("For exact patch versions, use: clickhousectl local install 25.12.9.61");
130141

131142
Ok(())
132143
}
133144

134145
async fn use_version(version_spec: &str) -> Result<()> {
135-
println!("Resolving version {}...", version_spec);
136-
let entry = version_manager::resolve_version(version_spec).await?;
137-
let version = &entry.version;
138-
139-
// Install if not already installed
140-
let installed = version_manager::list_installed_versions()?;
141-
if !installed.contains(version) {
142-
println!("Version {} not installed, installing...", version);
143-
version_manager::install_version(version, entry.channel).await?;
144-
}
146+
let spec = version_manager::parse_version_spec(version_spec)?;
147+
let platform = version_manager::platform::Platform::detect()?;
148+
149+
eprintln!("Resolving {}...", spec);
150+
let resolved = version_manager::resolve::resolve(&spec, &platform).await?;
151+
152+
// If exact version is known, check if already installed
153+
let version = if let Some(ref v) = resolved.exact_version {
154+
let installed = version_manager::list_installed_versions()?;
155+
if installed.contains(v) {
156+
v.clone()
157+
} else {
158+
eprintln!("Version {} not installed, installing...", v);
159+
version_manager::install::install_resolved(&resolved, &platform, false).await?
160+
}
161+
} else {
162+
// Version not known upfront (builds source) — install will detect it
163+
version_manager::install::install_resolved(&resolved, &platform, false).await?
164+
};
145165

146-
version_manager::set_default_version(version)?;
147-
println!("Default version set to {}", version);
166+
version_manager::set_default_version(&version)?;
167+
eprintln!("Default version set to {}", version);
148168
Ok(())
149169
}
150170

src/version_manager/download.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
11
use crate::error::{Error, Result};
2-
use crate::version_manager::list::Channel;
3-
use crate::version_manager::resolve::build_download_url;
2+
use crate::version_manager::platform::{DownloadSource, Platform};
43
use futures_util::StreamExt;
54
use indicatif::{ProgressBar, ProgressStyle};
65
use std::path::Path;
76
use tokio::io::AsyncWriteExt;
87

9-
/// Downloads a ClickHouse version to the specified path
10-
pub async fn download_version(version: &str, channel: Channel, dest_path: &Path) -> Result<()> {
11-
let url = build_download_url(version, channel)?;
8+
/// Downloads from a DownloadSource to the specified path
9+
pub async fn download_from_source(
10+
source: &DownloadSource,
11+
platform: &Platform,
12+
dest_path: &Path,
13+
) -> Result<()> {
14+
let url = source.url(platform);
15+
download_url(&url, dest_path).await
16+
}
17+
18+
/// Downloads a file from a URL to the specified path, with progress bar
19+
pub async fn download_url(url: &str, dest_path: &Path) -> Result<()> {
20+
let client = reqwest::Client::builder()
21+
.user_agent("clickhousectl")
22+
.build()?;
1223

13-
let client = reqwest::Client::new();
1424
let response = client
15-
.get(&url)
25+
.get(url)
1626
.send()
1727
.await?
1828
.error_for_status()

0 commit comments

Comments
 (0)