@@ -716,7 +716,7 @@ impl From<CliEditor> for openshell_cli::ssh::Editor {
716716#[ derive( Subcommand , Debug ) ]
717717enum ProviderCommands {
718718 /// Create a provider config.
719- #[ command( group = clap:: ArgGroup :: new( "cred_source" ) . required( true ) . args( [ "from_existing" , "credentials" , "from_gcloud_adc" , "runtime_credentials" ] ) , help_template = LEAF_HELP_TEMPLATE , next_help_heading = "FLAGS" ) ]
719+ #[ command( group = clap:: ArgGroup :: new( "cred_source" ) . required( true ) . multiple ( true ) . args( [ "from_existing" , "credentials" , "from_gcloud_adc" , "runtime_credentials" , "from_oidc_token "] ) , help_template = LEAF_HELP_TEMPLATE , next_help_heading = "FLAGS" ) ]
720720 Create {
721721 /// Provider name.
722722 #[ arg( long) ]
@@ -744,8 +744,12 @@ enum ProviderCommands {
744744 #[ arg( long, group = "cred_source" , conflicts_with_all = [ "from_existing" , "credentials" , "runtime_credentials" ] ) ]
745745 from_gcloud_adc : bool ,
746746
747+ /// Store the active gateway OIDC access token as the named provider credential.
748+ #[ arg( long, group = "cred_source" , conflicts_with_all = [ "from_existing" , "from_gcloud_adc" , "runtime_credentials" ] ) ]
749+ from_oidc_token : bool ,
750+
747751 /// Create a provider whose required credentials are resolved at runtime by the gateway/sandbox.
748- #[ arg( long, conflicts_with_all = [ "from_existing" , "credentials" , "from_gcloud_adc" ] ) ]
752+ #[ arg( long, conflicts_with_all = [ "from_existing" , "credentials" , "from_gcloud_adc" , "from_oidc_token" ] ) ]
749753 runtime_credentials : bool ,
750754
751755 /// Provider config key/value pair.
@@ -805,9 +809,13 @@ enum ProviderCommands {
805809 name : String ,
806810
807811 /// Re-discover credentials from existing local state (e.g. env vars, config files).
808- #[ arg( long, conflicts_with = "credentials" ) ]
812+ #[ arg( long, conflicts_with_all = [ "credentials" , "from_oidc_token" ] ) ]
809813 from_existing : bool ,
810814
815+ /// Store the active gateway OIDC access token as the named provider credential.
816+ #[ arg( long, conflicts_with = "from_existing" ) ]
817+ from_oidc_token : bool ,
818+
811819 /// Provider credential pair (`KEY=VALUE`) or env lookup key (`KEY`).
812820 #[ arg(
813821 long = "credential" ,
@@ -2845,20 +2853,44 @@ async fn main() -> Result<()> {
28452853 from_existing,
28462854 credentials,
28472855 from_gcloud_adc,
2856+ from_oidc_token,
28482857 runtime_credentials,
28492858 config,
28502859 } => {
2851- run:: provider_create_with_options (
2852- endpoint,
2853- & name,
2854- provider_type. as_str ( ) ,
2860+ let selected_sources = [
28552861 from_existing,
2856- & credentials,
28572862 from_gcloud_adc,
2863+ from_oidc_token,
28582864 runtime_credentials,
2859- & config,
2860- & tls,
2861- )
2865+ ]
2866+ . into_iter ( )
2867+ . filter ( |selected| * selected)
2868+ . count ( ) ;
2869+ if selected_sources > 1 {
2870+ return Err ( miette:: miette!(
2871+ "--from-existing, --from-gcloud-adc, --from-oidc-token, and --runtime-credentials are mutually exclusive"
2872+ ) ) ;
2873+ }
2874+ let credential_source = if from_existing {
2875+ run:: ProviderCreateCredentialSource :: Existing
2876+ } else if from_gcloud_adc {
2877+ run:: ProviderCreateCredentialSource :: GcloudAdc
2878+ } else if from_oidc_token {
2879+ run:: ProviderCreateCredentialSource :: OidcToken
2880+ } else if runtime_credentials {
2881+ run:: ProviderCreateCredentialSource :: Runtime
2882+ } else {
2883+ run:: ProviderCreateCredentialSource :: ExplicitCredentials
2884+ } ;
2885+ run:: provider_create_with_options ( run:: ProviderCreateOptions {
2886+ server : endpoint,
2887+ name : & name,
2888+ provider_type : provider_type. as_str ( ) ,
2889+ credentials : & credentials,
2890+ credential_source,
2891+ config : & config,
2892+ tls : & tls,
2893+ } )
28622894 . await ?;
28632895 }
28642896 ProviderCommands :: Refresh ( command) => match command {
@@ -2957,19 +2989,21 @@ async fn main() -> Result<()> {
29572989 ProviderCommands :: Update {
29582990 name,
29592991 from_existing,
2992+ from_oidc_token,
29602993 credentials,
29612994 config,
29622995 credential_expires_at,
29632996 } => {
2964- run:: provider_update (
2965- endpoint,
2966- & name,
2997+ run:: provider_update ( run :: ProviderUpdateOptions {
2998+ server : endpoint,
2999+ name : & name,
29673000 from_existing,
2968- & credentials,
2969- & config,
2970- & credential_expires_at,
2971- & tls,
2972- )
3001+ from_oidc_token,
3002+ credentials : & credentials,
3003+ config : & config,
3004+ credential_expires_at : & credential_expires_at,
3005+ tls : & tls,
3006+ } )
29733007 . await ?;
29743008 }
29753009 ProviderCommands :: Delete { names } => {
@@ -4050,6 +4084,68 @@ mod tests {
40504084 }
40514085 }
40524086
4087+ #[ test]
4088+ fn provider_create_accepts_from_oidc_token_destination_credential ( ) {
4089+ let cli = Cli :: try_parse_from ( [
4090+ "openshell" ,
4091+ "provider" ,
4092+ "create" ,
4093+ "--name" ,
4094+ "custom-api" ,
4095+ "--type" ,
4096+ "custom-api" ,
4097+ "--from-oidc-token" ,
4098+ "--credential" ,
4099+ "user_oidc_token" ,
4100+ ] )
4101+ . expect ( "provider create should parse from oidc token" ) ;
4102+
4103+ match cli. command {
4104+ Some ( Commands :: Provider {
4105+ command :
4106+ Some ( ProviderCommands :: Create {
4107+ name,
4108+ provider_type,
4109+ from_oidc_token,
4110+ credentials,
4111+ ..
4112+ } ) ,
4113+ } ) => {
4114+ assert_eq ! ( name, "custom-api" ) ;
4115+ assert_eq ! ( provider_type, "custom-api" ) ;
4116+ assert ! ( from_oidc_token) ;
4117+ assert_eq ! ( credentials, vec![ "user_oidc_token" ] ) ;
4118+ }
4119+ other => panic ! ( "expected provider create command, got: {other:?}" ) ,
4120+ }
4121+ }
4122+
4123+ #[ test]
4124+ fn provider_create_accepts_from_oidc_token_without_credential ( ) {
4125+ let cli = Cli :: try_parse_from ( [
4126+ "openshell" ,
4127+ "provider" ,
4128+ "create" ,
4129+ "--name" ,
4130+ "custom-api" ,
4131+ "--type" ,
4132+ "custom-api" ,
4133+ "--from-oidc-token" ,
4134+ ] )
4135+ . expect ( "provider create should parse inferred oidc token destination" ) ;
4136+
4137+ assert ! ( matches!(
4138+ cli. command,
4139+ Some ( Commands :: Provider {
4140+ command: Some ( ProviderCommands :: Create {
4141+ from_oidc_token: true ,
4142+ credentials,
4143+ ..
4144+ } )
4145+ } ) if credentials. is_empty( )
4146+ ) ) ;
4147+ }
4148+
40534149 #[ test]
40544150 fn provider_create_rejects_from_gcloud_adc_with_from_existing ( ) {
40554151 let err = Cli :: try_parse_from ( [
@@ -4210,6 +4306,56 @@ mod tests {
42104306 ) ) ;
42114307 }
42124308
4309+ #[ test]
4310+ fn provider_update_accepts_from_oidc_token_destination_credential ( ) {
4311+ let cli = Cli :: try_parse_from ( [
4312+ "openshell" ,
4313+ "provider" ,
4314+ "update" ,
4315+ "custom-api" ,
4316+ "--from-oidc-token" ,
4317+ "--credential" ,
4318+ "user_oidc_token" ,
4319+ ] )
4320+ . expect ( "provider update should parse from oidc token" ) ;
4321+
4322+ assert ! ( matches!(
4323+ cli. command,
4324+ Some ( Commands :: Provider {
4325+ command: Some ( ProviderCommands :: Update {
4326+ name,
4327+ from_oidc_token: true ,
4328+ credentials,
4329+ ..
4330+ } )
4331+ } ) if name == "custom-api" && credentials == vec![ "user_oidc_token" ]
4332+ ) ) ;
4333+ }
4334+
4335+ #[ test]
4336+ fn provider_update_accepts_from_oidc_token_without_credential ( ) {
4337+ let cli = Cli :: try_parse_from ( [
4338+ "openshell" ,
4339+ "provider" ,
4340+ "update" ,
4341+ "custom-api" ,
4342+ "--from-oidc-token" ,
4343+ ] )
4344+ . expect ( "provider update should parse inferred oidc token destination" ) ;
4345+
4346+ assert ! ( matches!(
4347+ cli. command,
4348+ Some ( Commands :: Provider {
4349+ command: Some ( ProviderCommands :: Update {
4350+ name,
4351+ from_oidc_token: true ,
4352+ credentials,
4353+ ..
4354+ } )
4355+ } ) if name == "custom-api" && credentials. is_empty( )
4356+ ) ) ;
4357+ }
4358+
42134359 #[ test]
42144360 fn provider_refresh_config_accepts_rfc3339_credential_expiry ( ) {
42154361 let cli = Cli :: try_parse_from ( [
0 commit comments