cedar_policy_cli/command/
link.rs1use std::{collections::HashMap, fs::OpenOptions, path::Path, str::FromStr};
18
19use cedar_policy::{PolicyId, SlotId};
20use clap::Args;
21use miette::{miette, IntoDiagnostic, Result};
22use serde::Deserialize;
23
24use crate::{
25 create_slot_env, load_links_from_file, parse_slot_id, CedarExitCode, PoliciesArgs,
26 TemplateLinked,
27};
28
29#[derive(Args, Debug)]
30pub struct LinkArgs {
31 #[command(flatten)]
33 pub policies: PoliciesArgs,
34 #[arg(long)]
36 pub template_id: String,
37 #[arg(short, long)]
39 pub new_id: String,
40 #[arg(short, long)]
42 pub arguments: Arguments,
43}
44
45#[derive(Clone, Debug, Deserialize)]
47#[serde(try_from = "HashMap<String,String>")]
48pub struct Arguments {
49 pub data: HashMap<SlotId, String>,
50}
51
52impl TryFrom<HashMap<String, String>> for Arguments {
53 type Error = String;
54
55 fn try_from(value: HashMap<String, String>) -> Result<Self, Self::Error> {
56 Ok(Self {
57 data: value
58 .into_iter()
59 .map(|(k, v)| parse_slot_id(k).map(|slot_id| (slot_id, v)))
60 .collect::<Result<HashMap<SlotId, String>, String>>()?,
61 })
62 }
63}
64
65impl FromStr for Arguments {
66 type Err = serde_json::Error;
67
68 fn from_str(s: &str) -> Result<Self, Self::Err> {
69 serde_json::from_str(s)
70 }
71}
72
73pub fn link(args: &LinkArgs) -> CedarExitCode {
74 if let Err(err) = link_inner(args) {
75 println!("{err:?}");
76 CedarExitCode::Failure
77 } else {
78 CedarExitCode::Success
79 }
80}
81
82fn link_inner(args: &LinkArgs) -> Result<()> {
83 let mut policies = args.policies.get_policy_set()?;
84 let slotenv = create_slot_env(&args.arguments.data)?;
85 policies.link(
86 PolicyId::new(&args.template_id),
87 PolicyId::new(&args.new_id),
88 slotenv,
89 )?;
90 let linked = policies
91 .policy(&PolicyId::new(&args.new_id))
92 .ok_or_else(|| miette!("Failed to find newly-added template-linked policy"))?;
93 println!("Template-linked policy added: {linked}");
94
95 if let Some(links_filename) = args.policies.template_linked_file.as_ref() {
97 update_template_linked_file(
98 links_filename,
99 TemplateLinked {
100 template_id: args.template_id.clone(),
101 link_id: args.new_id.clone(),
102 args: args.arguments.data.clone(),
103 },
104 )?;
105 }
106
107 Ok(())
108}
109
110fn update_template_linked_file(path: impl AsRef<Path>, new_linked: TemplateLinked) -> Result<()> {
112 let mut template_linked = load_links_from_file(path.as_ref())?;
113 template_linked.push(new_linked);
114 write_template_linked_file(&template_linked, path.as_ref())
115}
116
117fn write_template_linked_file(linked: &[TemplateLinked], path: impl AsRef<Path>) -> Result<()> {
119 let f = OpenOptions::new()
120 .write(true)
121 .truncate(true)
122 .create(true)
123 .open(path)
124 .into_diagnostic()?;
125 serde_json::to_writer(f, linked).into_diagnostic()
126}