-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathcmd_render.rs
179 lines (158 loc) · 6.12 KB
/
cmd_render.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// Copyright (c) Contributors to the SPK project.
// SPDX-License-Identifier: Apache-2.0
// https://github.com/spkenv/spk
use clap::builder::TypedValueParser;
use clap::Parser;
use miette::{Context, Result};
use spfs::prelude::*;
use spfs::storage::fallback::FallbackProxy;
use spfs::{graph, Error, RenderResult};
use spfs_cli_common as cli;
use spfs_cli_common::CommandName;
use strum::VariantNames;
cli::main!(CmdRender);
#[derive(Debug, Parser)]
pub struct CmdRender {
#[clap(flatten)]
sync: cli::Sync,
#[clap(flatten)]
render: cli::Render,
#[clap(flatten)]
logging: cli::Logging,
/// Allow re-rendering when the target directory is not empty
#[clap(long = "allow-existing")]
allow_existing: bool,
/// The strategy to use when rendering. Defaults to `Copy` when
/// using a local directory and `HardLink` for the repository.
#[clap(
long,
value_parser = clap::builder::PossibleValuesParser::new(spfs::storage::fs::CliRenderType::VARIANTS)
.map(|s| s.parse::<spfs::storage::fs::CliRenderType>().unwrap())
)]
strategy: Option<spfs::storage::fs::CliRenderType>,
/// The tag or digest of what to render, use a '+' to join multiple layers
reference: String,
/// Alternate path to render the manifest into (defaults to the local repository)
target: Option<std::path::PathBuf>,
}
impl CommandName for CmdRender {
fn command_name(&self) -> &'static str {
"render"
}
}
impl CmdRender {
pub async fn run(&mut self, config: &spfs::Config) -> Result<i32> {
let mut env_spec = spfs::tracking::EnvSpec::parse(&self.reference)?;
let (repo, origin, remotes) = tokio::try_join!(
config.get_opened_local_repository(),
config.try_get_remote("origin"),
config.list_remotes()
)?;
if let Some(origin) = origin {
let handle = repo.clone().into();
env_spec = self
.sync
.get_syncer(&origin, &handle)
.sync_env(env_spec)
.await?
.env;
}
// Use PayloadFallback to repair any missing payloads found in the
// local repository by copying from any of the configure remotes.
let fallback = FallbackProxy::new(repo, remotes);
let rendered = match &self.target {
Some(target) => self.render_to_dir(fallback, env_spec, target).await?,
None => self.render_to_repo(fallback, env_spec).await?,
};
tracing::debug!("render(s) completed successfully");
println!("{}", serde_json::json!(rendered));
Ok(0)
}
async fn render_to_dir(
&self,
repo: FallbackProxy,
env_spec: spfs::tracking::EnvSpec,
target: &std::path::Path,
) -> Result<RenderResult> {
tokio::fs::create_dir_all(&target)
.await
.map_err(|err| Error::RuntimeWriteError(target.to_owned(), err))?;
let target_dir = tokio::task::block_in_place(|| dunce::canonicalize(target))
.map_err(|err| Error::InvalidPath(target.to_owned(), err))?;
if tokio::fs::read_dir(&target_dir)
.await
.map_err(|err| Error::RuntimeReadError(target_dir.clone(), err))?
.next_entry()
.await
.map_err(|err| Error::RuntimeReadError(target_dir.clone(), err))?
.is_some()
&& !self.allow_existing
{
miette::bail!("Directory is not empty {}", target_dir.display());
}
tracing::info!("rendering into {}", target_dir.display());
let console_render_reporter = spfs::storage::fs::ConsoleRenderReporter::default();
let render_summary_reporter = spfs::storage::fs::RenderSummaryReporter::default();
let renderer = self.render.get_renderer(
&repo,
spfs::storage::fs::MultiReporter::new([
&console_render_reporter as &dyn spfs::storage::fs::RenderReporter,
&render_summary_reporter as &dyn spfs::storage::fs::RenderReporter,
]),
);
renderer
.render_into_directory(
env_spec,
&target_dir,
self.strategy
.map(Into::into)
.unwrap_or(spfs::storage::fs::RenderType::Copy),
)
.await?;
Ok(RenderResult {
paths_rendered: vec![target_dir],
render_summary: render_summary_reporter.into_summary(),
})
}
async fn render_to_repo(
&self,
repo: FallbackProxy,
env_spec: spfs::tracking::EnvSpec,
) -> Result<RenderResult> {
let mut stack = graph::Stack::default();
for env_item in env_spec.iter() {
let env_item = env_item.to_string();
let digest = repo
.resolve_ref(env_item.as_ref())
.await
.wrap_err_with(|| format!("resolve ref '{env_item}'"))?;
stack.push(digest);
}
let layers = spfs::resolve_stack_to_layers_with_repo(&stack, &repo)
.await
.wrap_err("resolve stack to layers")?;
let console_render_reporter = spfs::storage::fs::ConsoleRenderReporter::default();
let render_summary_reporter = spfs::storage::fs::RenderSummaryReporter::default();
let renderer = self.render.get_renderer(
&repo,
spfs::storage::fs::MultiReporter::new([
&console_render_reporter as &dyn spfs::storage::fs::RenderReporter,
&render_summary_reporter as &dyn spfs::storage::fs::RenderReporter,
]),
);
let stack = layers
.into_iter()
.filter_map(|l| l.manifest().copied())
.collect();
tracing::trace!("stack: {:?}", stack);
renderer
.render(&stack, self.strategy.map(Into::into))
.await
.map(|paths_rendered| RenderResult {
paths_rendered,
render_summary: render_summary_reporter.into_summary(),
})
.map_err(Into::<miette::Error>::into)
.wrap_err("render layers")
}
}