golem_cli/model/deploy_diff/
component.rs

1// Copyright 2024-2025 Golem Cloud
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::model::app::AppComponentName;
16use crate::model::component::Component;
17use crate::model::deploy_diff::DiffSerialize;
18use crate::model::text::component::is_sensitive_env_var_name;
19use crate::model::ComponentName;
20use golem_client::model::{DynamicLinkedInstance, DynamicLinking};
21use golem_common::model::{ComponentFilePermissions, ComponentType};
22use serde::{Deserialize, Serialize};
23use std::collections::{BTreeMap, HashMap};
24
25#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
26#[serde(rename_all = "camelCase")]
27pub struct DiffableComponentFile {
28    pub hash: String,
29    pub permissions: ComponentFilePermissions,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
33#[serde(rename_all = "camelCase")]
34pub struct DiffableComponent {
35    pub component_name: ComponentName,
36    pub component_hash: String,
37    pub component_type: ComponentType,
38    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
39    pub files: BTreeMap<String, DiffableComponentFile>,
40    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
41    pub dynamic_linking: BTreeMap<String, BTreeMap<String, String>>,
42    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
43    pub env: BTreeMap<String, String>,
44}
45
46impl DiffableComponent {
47    pub fn from_server(
48        show_sensitive: bool,
49        component: &Component,
50        component_hash: String,
51        files: BTreeMap<String, DiffableComponentFile>,
52    ) -> anyhow::Result<Self> {
53        Ok(Self {
54            component_name: component.component_name.clone(),
55            component_hash,
56            component_type: component.component_type,
57            files,
58            dynamic_linking: component
59                .metadata
60                .dynamic_linking
61                .iter()
62                .map(|(name, link)| {
63                    (
64                        name.clone(),
65                        match link {
66                            golem_common::model::component_metadata::DynamicLinkedInstance::WasmRpc(links) => links
67                                .targets
68                                .iter()
69                                .map(|(resource, target)| {
70                                    (resource.clone(), target.interface_name.clone())
71                                })
72                                .collect::<BTreeMap<String, String>>(),
73                        },
74                    )
75                })
76                .collect(),
77            env: masked_env(show_sensitive, &component.env),
78        })
79    }
80
81    pub fn from_manifest(
82        show_sensitive: bool,
83        component_name: &AppComponentName,
84        component_hash: String,
85        component_type: ComponentType,
86        files: BTreeMap<String, DiffableComponentFile>,
87        dynamic_linking: Option<&DynamicLinking>,
88        env: Option<&HashMap<String, String>>,
89    ) -> anyhow::Result<Self> {
90        Ok(DiffableComponent {
91            component_name: component_name.as_str().into(),
92            component_hash,
93            component_type,
94            files,
95            dynamic_linking: dynamic_linking
96                .iter()
97                .flat_map(|dl| {
98                    dl.dynamic_linking.iter().map(|(name, instance)| {
99                        (
100                            name.clone(),
101                            match instance {
102                                DynamicLinkedInstance::WasmRpc(links) => links
103                                    .targets
104                                    .iter()
105                                    .map(|(resource, target)| {
106                                        (resource.clone(), target.interface_name.clone())
107                                    })
108                                    .collect::<BTreeMap<_, _>>(),
109                            },
110                        )
111                    })
112                })
113                .collect(),
114            env: env
115                .map(|env| masked_env(show_sensitive, env))
116                .unwrap_or_default(),
117        })
118    }
119}
120
121impl DiffSerialize for DiffableComponent {
122    fn to_diffable_string(&self) -> anyhow::Result<String> {
123        Ok(serde_yaml::to_string(&self)?)
124    }
125}
126
127fn masked_env<'a, I: IntoIterator<Item = (&'a String, &'a String)>>(
128    show_sensitive: bool,
129    env: I,
130) -> BTreeMap<String, String> {
131    env.into_iter()
132        .map(|(k, v)| {
133            (
134                k.clone(),
135                if is_sensitive_env_var_name(show_sensitive, k) {
136                    format!("<hashed-value:{}>", blake3::hash(v.as_bytes()).to_hex())
137                } else {
138                    v.clone()
139                },
140            )
141        })
142        .collect()
143}