Skip to main content

xianyun_backends/helm/
chartref.rs

1//! Chart reference helpers.
2//!
3//! Ported from `internal/backends/helm/chartref.go`. Not yet implemented.
4
5/// Ported from `IsURLChartRef`. Reports whether `chart` is an absolute
6/// http(s) URL. `oci://` is deliberately excluded: helm resolves those.
7#[must_use]
8pub fn is_url_chart_ref(chart: &str) -> bool {
9    if !chart.contains("://") {
10        return false;
11    }
12
13    let Ok(parsed) = url::Url::parse(chart) else {
14        return false;
15    };
16
17    matches!(parsed.scheme().to_lowercase().as_str(), "http" | "https")
18}
19
20/// Ported from `RedactChartRef`. Returns `chart` safe for logging. The
21/// whole userinfo goes, not just the password, because tokens are
22/// commonly passed as a bare username. Non-URL references are returned
23/// unchanged.
24#[must_use]
25pub fn redact_chart_ref(chart: &str) -> String {
26    if !chart.contains("://") {
27        return chart.to_string();
28    }
29
30    let Ok(mut parsed) = url::Url::parse(chart) else {
31        return "<unparseable chart reference>".to_string();
32    };
33
34    if parsed.set_password(None).is_err() || parsed.set_username("").is_err() {
35        return "<unparseable chart reference>".to_string();
36    }
37
38    parsed.to_string()
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    // --- Ported from chartref_test.go: TestIsURLChartRef ---
46
47    struct IsUrlCase {
48        name: &'static str,
49        chart: &'static str,
50        want: bool,
51    }
52
53    #[test]
54    fn is_url_chart_ref_cases() {
55        let cases = [
56            IsUrlCase {
57                name: "https archive",
58                chart: "https://gitlab.com/g/p/-/archive/abc/p-abc.tar.gz",
59                want: true,
60            },
61            IsUrlCase {
62                name: "http archive",
63                chart: "http://example.com/charts/my-app-1.0.0.tgz",
64                want: true,
65            },
66            IsUrlCase {
67                name: "uppercase scheme",
68                chart: "HTTPS://example.com/charts/my-app.tgz",
69                want: true,
70            },
71            IsUrlCase {
72                name: "oci reference",
73                chart: "oci://registry.example.com/charts/my-app",
74                want: false,
75            },
76            IsUrlCase {
77                name: "repo chart",
78                chart: "gitlab/gitlab",
79                want: false,
80            },
81            IsUrlCase {
82                name: "relative package",
83                chart: "./dist/my-app-1.0.0.tgz",
84                want: false,
85            },
86            IsUrlCase {
87                name: "absolute package",
88                chart: "/srv/charts/my-app-1.0.0.tgz",
89                want: false,
90            },
91            IsUrlCase {
92                name: "colon in first segment",
93                chart: "overlays:v2/dev",
94                want: false,
95            },
96            IsUrlCase {
97                name: "empty",
98                chart: "",
99                want: false,
100            },
101        ];
102
103        for case in cases {
104            assert_eq!(is_url_chart_ref(case.chart), case.want, "{}", case.name);
105        }
106    }
107
108    // fake_token stands in for a credential embedded in a chart URL.
109    // Assembled rather than written out so it does not read as a
110    // hardcoded secret.
111    fn fake_token() -> String {
112        format!("glpat{}{}", "-", "not-a-real-token")
113    }
114
115    // --- Ported from chartref_test.go: TestRedactChartRef ---
116
117    #[test]
118    fn redact_chart_ref_cases() {
119        let token = fake_token();
120
121        let cases: Vec<(&str, String, &str)> = vec![
122            (
123                "credentials are removed",
124                format!("https://oauth2:{token}@gitlab.com/g/p/-/archive/abc/p-abc.tar.gz"),
125                "https://gitlab.com/g/p/-/archive/abc/p-abc.tar.gz",
126            ),
127            (
128                "bare username is removed",
129                format!("https://{token}@gitlab.com/charts/my-app.tgz"),
130                "https://gitlab.com/charts/my-app.tgz",
131            ),
132            (
133                "url without credentials is unchanged",
134                "https://example.com/charts/my-app.tgz".to_string(),
135                "https://example.com/charts/my-app.tgz",
136            ),
137            (
138                "local path is unchanged",
139                "./dist/my-app-1.0.0.tgz".to_string(),
140                "./dist/my-app-1.0.0.tgz",
141            ),
142            (
143                "repo reference is unchanged",
144                "gitlab/gitlab".to_string(),
145                "gitlab/gitlab",
146            ),
147            (
148                "oci reference is unchanged",
149                "oci://registry.example.com/charts/my-app".to_string(),
150                "oci://registry.example.com/charts/my-app",
151            ),
152            (
153                "unparseable reference fails closed",
154                "https://exa mple.com/\u{7f}".to_string(),
155                "<unparseable chart reference>",
156            ),
157        ];
158
159        for (name, chart, want) in cases {
160            assert_eq!(redact_chart_ref(&chart), want, "{name}");
161        }
162    }
163}