@@ -46,10 +46,10 @@ use openshell_core::proto::{
4646 ProviderCredentialRefreshStatus , ProviderCredentialRefreshStrategy , ProviderProfile ,
4747 ProviderProfileDiagnostic , ProviderProfileImportItem , RejectDraftChunkRequest ,
4848 RevokeSshSessionRequest , RotateProviderCredentialRequest , Sandbox , SandboxPhase , SandboxPolicy ,
49- SandboxSpec , SandboxTemplate , ServiceEndpointResponse , SetClusterInferenceRequest ,
50- SettingScope , SettingValue , TcpForwardFrame , TcpForwardInit , TcpRelayTarget ,
51- UpdateConfigRequest , UpdateProviderRequest , WatchSandboxRequest , exec_sandbox_event ,
52- setting_value, tcp_forward_init,
49+ SandboxPolicyRevision , SandboxSpec , SandboxTemplate , ServiceEndpointResponse ,
50+ SetClusterInferenceRequest , SettingScope , SettingValue , TcpForwardFrame , TcpForwardInit ,
51+ TcpRelayTarget , UpdateConfigRequest , UpdateProviderRequest , WatchSandboxRequest ,
52+ exec_sandbox_event , setting_value, tcp_forward_init,
5353} ;
5454use openshell_core:: settings:: { self , SettingValueKind } ;
5555use openshell_core:: { ObjectId , ObjectName } ;
@@ -6086,6 +6086,7 @@ pub async fn sandbox_policy_get(
60866086 name : & str ,
60876087 version : u32 ,
60886088 full : bool ,
6089+ json : bool ,
60896090 tls : & TlsOptions ,
60906091) -> Result < ( ) > {
60916092 let mut client = grpc_client ( server, tls) . await ?;
@@ -6101,6 +6102,17 @@ pub async fn sandbox_policy_get(
61016102
61026103 let inner = status_resp. into_inner ( ) ;
61036104 if let Some ( rev) = inner. revision {
6105+ if json {
6106+ print_policy_get_json (
6107+ "sandbox" ,
6108+ Some ( name) ,
6109+ Some ( inner. active_version ) ,
6110+ & rev,
6111+ full,
6112+ ) ?;
6113+ return Ok ( ( ) ) ;
6114+ }
6115+
61046116 let status = PolicyStatus :: try_from ( rev. status ) . unwrap_or ( PolicyStatus :: Unspecified ) ;
61056117 println ! ( "Version: {}" , rev. version) ;
61066118 println ! ( "Hash: {}" , rev. policy_hash) ;
@@ -6137,6 +6149,7 @@ pub async fn sandbox_policy_get_global(
61376149 server : & str ,
61386150 version : u32 ,
61396151 full : bool ,
6152+ json : bool ,
61406153 tls : & TlsOptions ,
61416154) -> Result < ( ) > {
61426155 let mut client = grpc_client ( server, tls) . await ?;
@@ -6152,6 +6165,11 @@ pub async fn sandbox_policy_get_global(
61526165
61536166 let inner = status_resp. into_inner ( ) ;
61546167 if let Some ( rev) = inner. revision {
6168+ if json {
6169+ print_policy_get_json ( "global" , None , None , & rev, full) ?;
6170+ return Ok ( ( ) ) ;
6171+ }
6172+
61556173 let status = PolicyStatus :: try_from ( rev. status ) . unwrap_or ( PolicyStatus :: Unspecified ) ;
61566174 println ! ( "Scope: global" ) ;
61576175 println ! ( "Version: {}" , rev. version) ;
@@ -6181,6 +6199,96 @@ pub async fn sandbox_policy_get_global(
61816199 Ok ( ( ) )
61826200}
61836201
6202+ fn print_policy_get_json (
6203+ scope : & str ,
6204+ name : Option < & str > ,
6205+ active_version : Option < u32 > ,
6206+ revision : & SandboxPolicyRevision ,
6207+ include_policy : bool ,
6208+ ) -> Result < ( ) > {
6209+ let value = policy_get_json_value ( scope, name, active_version, revision, include_policy) ?;
6210+ println ! (
6211+ "{}" ,
6212+ serde_json:: to_string_pretty( & value) . into_diagnostic( ) ?
6213+ ) ;
6214+ Ok ( ( ) )
6215+ }
6216+
6217+ fn policy_get_json_value (
6218+ scope : & str ,
6219+ name : Option < & str > ,
6220+ active_version : Option < u32 > ,
6221+ revision : & SandboxPolicyRevision ,
6222+ include_policy : bool ,
6223+ ) -> Result < serde_json:: Value > {
6224+ let mut revision_obj = serde_json:: Map :: new ( ) ;
6225+ revision_obj. insert ( "version" . to_string ( ) , serde_json:: json!( revision. version) ) ;
6226+ revision_obj. insert ( "hash" . to_string ( ) , serde_json:: json!( revision. policy_hash) ) ;
6227+ revision_obj. insert (
6228+ "status" . to_string ( ) ,
6229+ serde_json:: json!( policy_status_json_name( revision. status) ) ,
6230+ ) ;
6231+ revision_obj. insert (
6232+ "created_at_ms" . to_string ( ) ,
6233+ serde_json:: json!( revision. created_at_ms) ,
6234+ ) ;
6235+ revision_obj. insert (
6236+ "loaded_at_ms" . to_string ( ) ,
6237+ serde_json:: json!( revision. loaded_at_ms) ,
6238+ ) ;
6239+ if !revision. load_error . is_empty ( ) {
6240+ revision_obj. insert (
6241+ "load_error" . to_string ( ) ,
6242+ serde_json:: json!( revision. load_error) ,
6243+ ) ;
6244+ }
6245+ if include_policy {
6246+ let policy = revision
6247+ . policy
6248+ . as_ref ( )
6249+ . map ( sandbox_policy_json_value)
6250+ . transpose ( ) ?
6251+ . unwrap_or ( serde_json:: Value :: Null ) ;
6252+ revision_obj. insert ( "policy" . to_string ( ) , policy) ;
6253+ }
6254+
6255+ let mut root = serde_json:: Map :: new ( ) ;
6256+ root. insert ( "scope" . to_string ( ) , serde_json:: json!( scope) ) ;
6257+ if let Some ( name) = name {
6258+ root. insert ( "name" . to_string ( ) , serde_json:: json!( name) ) ;
6259+ }
6260+ if let Some ( active_version) = active_version {
6261+ root. insert (
6262+ "active_version" . to_string ( ) ,
6263+ serde_json:: json!( active_version) ,
6264+ ) ;
6265+ }
6266+ root. insert (
6267+ "revision" . to_string ( ) ,
6268+ serde_json:: Value :: Object ( revision_obj) ,
6269+ ) ;
6270+
6271+ Ok ( serde_json:: Value :: Object ( root) )
6272+ }
6273+
6274+ fn policy_status_json_name ( status : i32 ) -> & ' static str {
6275+ match PolicyStatus :: try_from ( status) . unwrap_or ( PolicyStatus :: Unspecified ) {
6276+ PolicyStatus :: Unspecified => "unspecified" ,
6277+ PolicyStatus :: Pending => "pending" ,
6278+ PolicyStatus :: Loaded => "loaded" ,
6279+ PolicyStatus :: Failed => "failed" ,
6280+ PolicyStatus :: Superseded => "superseded" ,
6281+ }
6282+ }
6283+
6284+ fn sandbox_policy_json_value ( policy : & SandboxPolicy ) -> Result < serde_json:: Value > {
6285+ let yaml_str = openshell_policy:: serialize_sandbox_policy ( policy)
6286+ . wrap_err ( "failed to serialize policy to YAML" ) ?;
6287+ serde_yml:: from_str ( & yaml_str)
6288+ . into_diagnostic ( )
6289+ . wrap_err ( "failed to convert policy to JSON" )
6290+ }
6291+
61846292pub async fn sandbox_policy_list (
61856293 server : & str ,
61866294 name : & str ,
@@ -6232,7 +6340,7 @@ pub async fn sandbox_policy_list_global(server: &str, limit: u32, tls: &TlsOptio
62326340 Ok ( ( ) )
62336341}
62346342
6235- fn print_policy_revision_table ( revisions : & [ openshell_core :: proto :: SandboxPolicyRevision ] ) {
6343+ fn print_policy_revision_table ( revisions : & [ SandboxPolicyRevision ] ) {
62366344 println ! (
62376345 "{:<8} {:<14} {:<12} {:<24} ERROR" ,
62386346 "VERSION" , "HASH" , "STATUS" , "CREATED"
@@ -6711,7 +6819,7 @@ mod tests {
67116819 git_sync_files, http_health_check, image_requests_gpu, import_local_package_mtls_bundle,
67126820 inferred_provider_type, package_managed_tls_dirs, parse_cli_setting_value,
67136821 parse_credential_expiry_cli_value, parse_credential_expiry_pairs, parse_credential_pairs,
6714- plaintext_gateway_is_remote, progress_step_from_metadata,
6822+ plaintext_gateway_is_remote, policy_get_json_value , progress_step_from_metadata,
67156823 provider_profile_allows_refresh_bootstrap, provisioning_timeout_message,
67166824 ready_false_condition_message, refresh_status_header, refresh_status_row, resolve_from,
67176825 sandbox_should_persist, service_expose_status_error, service_url_for_gateway,
@@ -6733,9 +6841,10 @@ mod tests {
67336841 PROGRESS_STEP_STARTING_SANDBOX ,
67346842 } ;
67356843 use openshell_core:: proto:: {
6736- Provider , ProviderCredentialRefresh , ProviderCredentialRefreshStatus ,
6737- ProviderCredentialRefreshStrategy , ProviderProfile , ProviderProfileCredential ,
6738- SandboxCondition , SandboxStatus , datamodel:: v1:: ObjectMeta ,
6844+ FilesystemPolicy , PolicyStatus , Provider , ProviderCredentialRefresh ,
6845+ ProviderCredentialRefreshStatus , ProviderCredentialRefreshStrategy , ProviderProfile ,
6846+ ProviderProfileCredential , SandboxCondition , SandboxPolicy , SandboxPolicyRevision ,
6847+ SandboxStatus , datamodel:: v1:: ObjectMeta ,
67396848 } ;
67406849
67416850 struct EnvVarGuard {
@@ -7112,6 +7221,60 @@ mod tests {
71127221 assert ! ( build_sandbox_resource_limits( None , Some ( "1.5Gi" ) ) . is_err( ) ) ;
71137222 }
71147223
7224+ #[ test]
7225+ fn policy_get_json_includes_metadata_and_full_policy_when_requested ( ) {
7226+ let revision = SandboxPolicyRevision {
7227+ version : 7 ,
7228+ policy_hash : "sha256:test" . to_string ( ) ,
7229+ status : PolicyStatus :: Loaded as i32 ,
7230+ created_at_ms : 10 ,
7231+ loaded_at_ms : 20 ,
7232+ policy : Some ( SandboxPolicy {
7233+ version : 1 ,
7234+ filesystem : Some ( FilesystemPolicy {
7235+ include_workdir : true ,
7236+ read_only : vec ! [ "/usr" . to_string( ) ] ,
7237+ read_write : vec ! [ "/sandbox" . to_string( ) ] ,
7238+ } ) ,
7239+ ..Default :: default ( )
7240+ } ) ,
7241+ ..Default :: default ( )
7242+ } ;
7243+
7244+ let value = policy_get_json_value ( "sandbox" , Some ( "demo" ) , Some ( 7 ) , & revision, true )
7245+ . expect ( "policy JSON should render" ) ;
7246+
7247+ assert_eq ! ( value[ "scope" ] . as_str( ) , Some ( "sandbox" ) ) ;
7248+ assert_eq ! ( value[ "name" ] . as_str( ) , Some ( "demo" ) ) ;
7249+ assert_eq ! ( value[ "active_version" ] . as_u64( ) , Some ( 7 ) ) ;
7250+ assert_eq ! ( value[ "revision" ] [ "version" ] . as_u64( ) , Some ( 7 ) ) ;
7251+ assert_eq ! ( value[ "revision" ] [ "hash" ] . as_str( ) , Some ( "sha256:test" ) ) ;
7252+ assert_eq ! ( value[ "revision" ] [ "status" ] . as_str( ) , Some ( "loaded" ) ) ;
7253+ assert_eq ! ( value[ "revision" ] [ "policy" ] [ "version" ] . as_u64( ) , Some ( 1 ) ) ;
7254+ assert_eq ! (
7255+ value[ "revision" ] [ "policy" ] [ "filesystem_policy" ] [ "read_write" ] [ 0 ] . as_str( ) ,
7256+ Some ( "/sandbox" )
7257+ ) ;
7258+ }
7259+
7260+ #[ test]
7261+ fn policy_get_json_omits_policy_without_full ( ) {
7262+ let revision = SandboxPolicyRevision {
7263+ version : 3 ,
7264+ policy_hash : "sha256:test" . to_string ( ) ,
7265+ status : PolicyStatus :: Pending as i32 ,
7266+ ..Default :: default ( )
7267+ } ;
7268+
7269+ let value = policy_get_json_value ( "global" , None , None , & revision, false )
7270+ . expect ( "policy JSON should render" ) ;
7271+
7272+ assert_eq ! ( value[ "scope" ] . as_str( ) , Some ( "global" ) ) ;
7273+ assert ! ( value. get( "name" ) . is_none( ) ) ;
7274+ assert ! ( value. get( "active_version" ) . is_none( ) ) ;
7275+ assert ! ( value[ "revision" ] . get( "policy" ) . is_none( ) ) ;
7276+ }
7277+
71157278 #[ test]
71167279 fn inferred_provider_type_returns_type_for_known_command ( ) {
71177280 let result = inferred_provider_type ( & [ "claude" . to_string ( ) , "--help" . to_string ( ) ] ) ;
0 commit comments