@@ -33,6 +33,29 @@ pub struct Oci {
3333 pub version : TemplateableValue < String > ,
3434 /// Public key url is expected to be a jwks.
3535 pub public_key_url : Option < TemplateableValue < String > > ,
36+ /// Postdownload script to execute after downloading and extracting the package.
37+ /// All validations, checks, and installation steps should go here.
38+ pub postdownload : Option < Postdownload > ,
39+ }
40+
41+ #[ derive( Debug , Deserialize , Clone , PartialEq ) ]
42+ pub struct Postdownload {
43+ /// Arguments where first element is the command/executable (e.g., "bash", "sh", "python3"),
44+ /// followed by script path and additional arguments.
45+ /// Example: ["bash", "./scripts/postdownload.sh", "-e"]
46+ pub args : Vec < TemplateableValue < String > > ,
47+
48+ /// Environmental variables passed to the process.
49+ #[ serde( default ) ]
50+ pub env : std:: collections:: HashMap < String , TemplateableValue < String > > ,
51+
52+ /// Maximum time to wait for the script to complete.
53+ #[ serde( default = "default_postdownload_timeout" ) ]
54+ pub timeout : TemplateableValue < String > ,
55+ }
56+
57+ fn default_postdownload_timeout ( ) -> TemplateableValue < String > {
58+ TemplateableValue :: new ( "300s" . to_string ( ) )
3659}
3760
3861impl Templateable for Package {
@@ -78,14 +101,52 @@ impl Templateable for Oci {
78101 AgentTypeError :: OCIReferenceParsingError ( format ! ( "invalid public_key_url: {err}" ) )
79102 } ) ?;
80103
104+ let postdownload = self
105+ . postdownload
106+ . map ( |pd| pd. template_with ( variables) )
107+ . transpose ( ) ?;
108+
81109 Ok ( Self :: Output {
82110 repository,
83111 version,
84112 public_key_url,
113+ postdownload,
85114 } )
86115 }
87116}
88117
118+ impl Templateable for Postdownload {
119+ type Output = rendered:: Postdownload ;
120+ fn template_with ( self , variables : & Variables ) -> Result < Self :: Output , AgentTypeError > {
121+ let timeout_str = self . timeout . template_with ( variables) ?;
122+
123+ let args: Vec < String > = self
124+ . args
125+ . into_iter ( )
126+ . map ( |arg| arg. template_with ( variables) )
127+ . collect :: < Result < Vec < String > , AgentTypeError > > ( ) ?;
128+
129+ if args. is_empty ( ) {
130+ return Err ( AgentTypeError :: OCIReferenceParsingError (
131+ "postdownload args cannot be empty, first element must be the command/executable"
132+ . to_string ( ) ,
133+ ) ) ;
134+ }
135+
136+ let env: std:: collections:: HashMap < String , String > = self
137+ . env
138+ . into_iter ( )
139+ . map ( |( k, v) | v. template_with ( variables) . map ( |templated| ( k, templated) ) )
140+ . collect :: < Result < std:: collections:: HashMap < _ , _ > , AgentTypeError > > ( ) ?;
141+
142+ let timeout = duration_str:: parse ( & timeout_str) . map_err ( |err| {
143+ AgentTypeError :: OCIReferenceParsingError ( format ! ( "invalid timeout format: {err}" ) )
144+ } ) ?;
145+
146+ Ok ( Self :: Output { args, env, timeout } )
147+ }
148+ }
149+
89150#[ cfg( test) ]
90151mod tests {
91152 use std:: str:: FromStr ;
@@ -130,6 +191,7 @@ mod tests {
130191 public_key_url : public_key_url
131192 . clone ( )
132193 . map ( |_| TemplateableValue :: from_template ( "${nr-var:public-key}" . to_string ( ) ) ) ,
194+ postdownload : None ,
133195 } ;
134196
135197 let rendered_oci = oci. template_with ( & variables) . unwrap ( ) ;
0 commit comments