Skip to content

Commit 3d58abe

Browse files
committed
default readOnly
Signed-off-by: Daniel Guns <danbguns@gmail.com>
1 parent ca00608 commit 3d58abe

17 files changed

Lines changed: 2012 additions & 178 deletions

File tree

examples/themes/dracula.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,4 @@ k9s:
4646
fg: "#f8f8f2"
4747
bg: "#282a36"
4848

49+

examples/themes/solarized-dark.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,4 @@ k9s:
4646
fg: "#839496"
4747
bg: "#002b36"
4848

49+

src/config/defaults.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ mod tests {
1616
#[test]
1717
fn test_default_config() {
1818
let config = default_config();
19-
assert!(!config.read_only);
19+
assert!(config.read_only);
2020
assert_eq!(config.default_namespace, "flux-system");
2121
}
2222
}

src/config/loader.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ mod tests {
153153
#[test]
154154
fn test_load_defaults() {
155155
let config = ConfigLoader::load_defaults();
156-
assert!(!config.read_only);
156+
assert!(config.read_only);
157157
assert_eq!(config.default_namespace, "flux-system");
158158
}
159159

src/config/schema.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ pub struct LoggerConfig {
7878

7979
// Default value functions
8080
fn default_read_only() -> bool {
81-
false
81+
true
8282
}
8383

8484
fn default_namespace() -> String {
@@ -151,7 +151,7 @@ mod tests {
151151
#[test]
152152
fn test_config_defaults() {
153153
let config = Config::default();
154-
assert!(!config.read_only);
154+
assert!(config.read_only);
155155
assert_eq!(config.default_namespace, "flux-system");
156156
assert_eq!(config.ui.skin, "default");
157157
}

src/config/theme_loader.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,14 @@ impl ThemeLoader {
169169
return Ok(Theme::default());
170170
}
171171

172-
// If theme not found and not "default", try loading "default" first, then apply overrides
173-
// For now, just return default
174-
tracing::warn!("Theme '{}' not found, using default theme", name);
175-
Ok(Theme::default())
172+
Err(anyhow::anyhow!(
173+
"Theme '{}' not found in either \n '{}'\n or '{}'",
174+
name,
175+
paths::user_skins_dir()
176+
.join(format!("{}.yaml", name))
177+
.display(),
178+
paths::skins_dir().join(format!("{}.yaml", name)).display()
179+
))
176180
}
177181

178182
/// Load theme from a YAML file

src/config/theme_test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@ mod tests {
3333
}
3434
}
3535

36+

src/tui/api.rs

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
use anyhow::Result;
2+
use kube::core::{ApiResource, DynamicObject};
3+
use kube::Api;
4+
5+
use crate::watcher::WatchableResource;
6+
use crate::watcher::{
7+
Alert, Bucket, ExternalArtifact, GitRepository, HelmChart, HelmRelease, HelmRepository,
8+
ImagePolicy, ImageRepository, ImageUpdateAutomation, Kustomization, OCIRepository, Provider,
9+
Receiver,
10+
};
11+
12+
/// Get GroupVersionKind for a resource type
13+
pub fn get_gvk_for_resource_type(resource_type: &str) -> Result<(String, String, String)> {
14+
let (group, version, plural) = match resource_type {
15+
"GitRepository" => (
16+
GitRepository::api_group(),
17+
GitRepository::api_version(),
18+
GitRepository::plural(),
19+
),
20+
"OCIRepository" => (
21+
OCIRepository::api_group(),
22+
OCIRepository::api_version(),
23+
OCIRepository::plural(),
24+
),
25+
"HelmRepository" => (
26+
HelmRepository::api_group(),
27+
HelmRepository::api_version(),
28+
HelmRepository::plural(),
29+
),
30+
"Bucket" => (Bucket::api_group(), Bucket::api_version(), Bucket::plural()),
31+
"HelmChart" => (
32+
HelmChart::api_group(),
33+
HelmChart::api_version(),
34+
HelmChart::plural(),
35+
),
36+
"ExternalArtifact" => (
37+
ExternalArtifact::api_group(),
38+
ExternalArtifact::api_version(),
39+
ExternalArtifact::plural(),
40+
),
41+
"Kustomization" => (
42+
Kustomization::api_group(),
43+
Kustomization::api_version(),
44+
Kustomization::plural(),
45+
),
46+
"HelmRelease" => (
47+
HelmRelease::api_group(),
48+
HelmRelease::api_version(),
49+
HelmRelease::plural(),
50+
),
51+
"ImageRepository" => (
52+
ImageRepository::api_group(),
53+
ImageRepository::api_version(),
54+
ImageRepository::plural(),
55+
),
56+
"ImagePolicy" => (
57+
ImagePolicy::api_group(),
58+
ImagePolicy::api_version(),
59+
ImagePolicy::plural(),
60+
),
61+
"ImageUpdateAutomation" => (
62+
ImageUpdateAutomation::api_group(),
63+
ImageUpdateAutomation::api_version(),
64+
ImageUpdateAutomation::plural(),
65+
),
66+
"Alert" => (Alert::api_group(), Alert::api_version(), Alert::plural()),
67+
"Provider" => (
68+
Provider::api_group(),
69+
Provider::api_version(),
70+
Provider::plural(),
71+
),
72+
"Receiver" => (
73+
Receiver::api_group(),
74+
Receiver::api_version(),
75+
Receiver::plural(),
76+
),
77+
// Handle standard Kubernetes resources
78+
"Deployment" | "Service" | "ConfigMap" | "Secret" | "Pod" | "Namespace" => {
79+
return Ok((
80+
"".to_string(),
81+
"v1".to_string(),
82+
resource_type.to_lowercase() + "s",
83+
));
84+
}
85+
_ => return Err(anyhow::anyhow!("Unknown resource type: {}", resource_type)),
86+
};
87+
88+
Ok((group.to_string(), version.to_string(), plural.to_string()))
89+
}
90+
91+
/// Generate fallback API versions based on the default version
92+
///
93+
/// This generates common fallback versions without hardcoding specific resource types.
94+
/// For example, if default is "v1", it will try "v1beta2", "v1beta1", "v1alpha1".
95+
/// If default is "v2", it will try "v2beta2", "v2beta1", "v1", "v1beta2", etc.
96+
fn generate_fallback_versions(default_version: &str) -> Vec<String> {
97+
let mut fallbacks = Vec::new();
98+
99+
// Parse version (e.g., "v1", "v2beta1", "v1beta2")
100+
if let Some(version_num) = default_version.strip_prefix('v') {
101+
// Extract major version and suffix
102+
let parts: Vec<&str> = version_num.splitn(2, |c: char| c.is_alphabetic()).collect();
103+
let major = parts[0].parse::<u32>().unwrap_or(1);
104+
let suffix = if parts.len() > 1 { parts[1] } else { "" };
105+
106+
// If it's a stable version (v1, v2, etc.), generate beta/alpha fallbacks
107+
if suffix.is_empty() {
108+
// For v1: try v1beta2, v1beta1, v1alpha1
109+
fallbacks.push(format!("v{}beta2", major));
110+
fallbacks.push(format!("v{}beta1", major));
111+
fallbacks.push(format!("v{}alpha1", major));
112+
113+
// Also try previous major version's stable and betas
114+
if major > 1 {
115+
let prev_major = major - 1;
116+
fallbacks.push(format!("v{}", prev_major));
117+
fallbacks.push(format!("v{}beta2", prev_major));
118+
fallbacks.push(format!("v{}beta1", prev_major));
119+
}
120+
} else if suffix.starts_with("beta") {
121+
// If it's a beta version, try other beta versions and alpha
122+
let beta_num = suffix
123+
.strip_prefix("beta")
124+
.and_then(|s| s.parse::<u32>().ok())
125+
.unwrap_or(1);
126+
if beta_num > 1 {
127+
fallbacks.push(format!("v{}beta{}", major, beta_num - 1));
128+
}
129+
fallbacks.push(format!("v{}beta1", major));
130+
fallbacks.push(format!("v{}alpha1", major));
131+
132+
// Also try previous major version
133+
if major > 1 {
134+
fallbacks.push(format!("v{}", major - 1));
135+
}
136+
} else if suffix.starts_with("alpha") {
137+
// If it's an alpha version, try other alpha versions
138+
let alpha_num = suffix
139+
.strip_prefix("alpha")
140+
.and_then(|s| s.parse::<u32>().ok())
141+
.unwrap_or(1);
142+
if alpha_num > 1 {
143+
fallbacks.push(format!("v{}alpha{}", major, alpha_num - 1));
144+
}
145+
fallbacks.push(format!("v{}alpha1", major));
146+
}
147+
}
148+
149+
fallbacks
150+
}
151+
152+
/// Get ApiResource for a resource type with version fallback
153+
///
154+
/// **Why kubectl works without versions but kube-rs doesn't:**
155+
/// - kubectl uses Kubernetes API discovery (`/apis` endpoint) to find all available versions
156+
/// and automatically selects the preferred version or converts between versions
157+
/// - kube-rs requires explicit ApiResource specification, but we can discover versions
158+
/// by trying them (which is what this function does)
159+
///
160+
/// This function tries the default version first, then falls back to older versions if needed.
161+
/// Fallback versions are generated dynamically based on the default version, avoiding hardcoded
162+
/// version lists for specific resource types.
163+
pub async fn get_api_resource_with_fallback(
164+
client: &kube::Client,
165+
resource_type: &str,
166+
namespace: &str,
167+
name: &str,
168+
) -> Result<ApiResource> {
169+
// Get default group, version, and plural
170+
let (group, default_version, plural) = get_gvk_for_resource_type(resource_type)?;
171+
172+
// For standard Kubernetes resources, use default version
173+
if group.is_empty() {
174+
return Ok(ApiResource {
175+
group: group.clone(),
176+
version: default_version.clone(),
177+
api_version: format!("{}/{}", group, default_version),
178+
kind: resource_type.to_string(),
179+
plural: plural.clone(),
180+
});
181+
}
182+
183+
// Try default version first (usually v1, the newest)
184+
let api_resource = ApiResource {
185+
group: group.clone(),
186+
version: default_version.clone(),
187+
api_version: format!("{}/{}", group, default_version),
188+
kind: resource_type.to_string(),
189+
plural: plural.clone(),
190+
};
191+
192+
let api: Api<DynamicObject> = Api::namespaced_with(client.clone(), namespace, &api_resource);
193+
194+
// Try to get the resource with default version
195+
match api.get(name).await {
196+
Ok(_) => {
197+
// Default version works!
198+
return Ok(api_resource);
199+
}
200+
Err(e) => {
201+
let error_string = format!("{}", e);
202+
// If it's not a 404, return the error (resource might not exist or other issue)
203+
if !error_string.contains("404") && !error_string.contains("Not Found") {
204+
return Err(anyhow::anyhow!("Failed to fetch {}: {}", resource_type, e));
205+
}
206+
// 404 means this version doesn't exist, try fallback versions
207+
}
208+
}
209+
210+
// Generate fallback versions dynamically based on the default version
211+
// This avoids hardcoding specific resource types and versions
212+
let fallback_versions = generate_fallback_versions(&default_version);
213+
214+
// Try fallback versions
215+
for version in fallback_versions {
216+
let fallback_api_resource = ApiResource {
217+
group: group.clone(),
218+
version: version.clone(),
219+
api_version: format!("{}/{}", group, version),
220+
kind: resource_type.to_string(),
221+
plural: plural.clone(),
222+
};
223+
224+
let fallback_api: Api<DynamicObject> =
225+
Api::namespaced_with(client.clone(), namespace, &fallback_api_resource);
226+
227+
match fallback_api.get(name).await {
228+
Ok(_) => {
229+
// This version works!
230+
tracing::debug!(
231+
"Using fallback version {} for {} (default was {})",
232+
version,
233+
resource_type,
234+
default_version
235+
);
236+
return Ok(fallback_api_resource);
237+
}
238+
Err(e) => {
239+
let error_string = format!("{}", e);
240+
// If it's not a 404, this might be the right version but resource doesn't exist
241+
// Continue trying other versions
242+
if !error_string.contains("404") && !error_string.contains("Not Found") {
243+
// Non-404 error - might be the right version, return it
244+
return Ok(fallback_api_resource);
245+
}
246+
}
247+
}
248+
}
249+
250+
// If we get here, default version didn't work and no fallback worked
251+
// Return the default anyway - the error will be handled by the caller
252+
Ok(api_resource)
253+
}

0 commit comments

Comments
 (0)