@@ -7,6 +7,7 @@ use cli_table::{print_stderr, WithTitle};
7
7
use include_dir:: { include_dir, Dir } ;
8
8
use miette:: { bail, miette, Context , IntoDiagnostic , Result } ;
9
9
use once_cell:: sync:: Lazy ;
10
+ use secretspec;
10
11
use serde:: Deserialize ;
11
12
use serde_json;
12
13
use sha2:: Digest ;
@@ -90,6 +91,9 @@ pub struct Devenv {
90
91
91
92
has_processes : Arc < OnceCell < bool > > ,
92
93
94
+ // Secretspec resolved data to pass to Nix
95
+ secretspec_resolved : Arc < OnceCell < secretspec:: Resolved < HashMap < String , String > > > > ,
96
+
93
97
// TODO: make private.
94
98
// Pass as an arg or have a setter.
95
99
pub container_name : Option < String > ,
@@ -145,11 +149,19 @@ impl Devenv {
145
149
cachix_trusted_keys,
146
150
} ;
147
151
152
+ // Create shared secretspec_resolved Arc to share between Devenv and Nix
153
+ let secretspec_resolved = Arc :: new ( OnceCell :: new ( ) ) ;
154
+
148
155
let nix: Box < dyn nix_backend:: NixBackend > = match backend_type {
149
156
config:: NixBackendType :: Nix => Box :: new (
150
- crate :: nix:: Nix :: new ( options. config . clone ( ) , global_options. clone ( ) , paths)
151
- . await
152
- . expect ( "Failed to initialize Nix backend" ) ,
157
+ crate :: nix:: Nix :: new (
158
+ options. config . clone ( ) ,
159
+ global_options. clone ( ) ,
160
+ paths,
161
+ secretspec_resolved. clone ( ) ,
162
+ )
163
+ . await
164
+ . expect ( "Failed to initialize Nix backend" ) ,
153
165
) ,
154
166
#[ cfg( feature = "snix" ) ]
155
167
config:: NixBackendType :: Snix => Box :: new (
@@ -176,6 +188,7 @@ impl Devenv {
176
188
assembled : Arc :: new ( AtomicBool :: new ( false ) ) ,
177
189
assemble_lock : Arc :: new ( Semaphore :: new ( 1 ) ) ,
178
190
has_processes : Arc :: new ( OnceCell :: new ( ) ) ,
191
+ secretspec_resolved,
179
192
container_name : None ,
180
193
}
181
194
}
@@ -1201,6 +1214,79 @@ impl Devenv {
1201
1214
miette:: miette!( "Failed to create {}: {}" , self . devenv_runtime. display( ) , e)
1202
1215
} ) ?;
1203
1216
1217
+ // Check for secretspec.toml and load secrets
1218
+ let secretspec_path = self . devenv_root . join ( "secretspec.toml" ) ;
1219
+ let secretspec_config_exists = config. secretspec . is_some ( ) ;
1220
+ let secretspec_enabled = config
1221
+ . secretspec
1222
+ . as_ref ( )
1223
+ . map ( |c| c. enable )
1224
+ . unwrap_or ( false ) ; // Default to false if secretspec config is not present
1225
+
1226
+ if secretspec_path. exists ( ) {
1227
+ // Log warning when secretspec.toml exists but is not configured
1228
+ if !secretspec_enabled && !secretspec_config_exists {
1229
+ info ! (
1230
+ "{}" ,
1231
+ indoc:: formatdoc! { "
1232
+ Found secretspec.toml but secretspec integration is not enabled.
1233
+
1234
+ To enable, add to devenv.yaml:
1235
+ secretspec:
1236
+ enable: true
1237
+
1238
+ To disable this message:
1239
+ secretspec:
1240
+ enable: false
1241
+
1242
+ Learn more: https://devenv.sh/integrations/secretspec/
1243
+ " }
1244
+ ) ;
1245
+ }
1246
+
1247
+ if secretspec_enabled {
1248
+ // Get profile and provider from devenv.yaml config
1249
+ let ( profile, provider) = if let Some ( ref secretspec_config) = config. secretspec {
1250
+ (
1251
+ secretspec_config. profile . clone ( ) ,
1252
+ secretspec_config. provider . clone ( ) ,
1253
+ )
1254
+ } else {
1255
+ ( None , None )
1256
+ } ;
1257
+
1258
+ // Load and validate secrets using SecretSpec API
1259
+ let mut secrets = secretspec:: Secrets :: load ( )
1260
+ . map_err ( |e| miette ! ( "Failed to load secretspec configuration: {}" , e) ) ?;
1261
+
1262
+ // Configure provider and profile if specified
1263
+ if let Some ( ref provider_str) = provider {
1264
+ secrets. set_provider ( provider_str) ;
1265
+ }
1266
+ if let Some ( ref profile_str) = profile {
1267
+ secrets. set_profile ( profile_str) ;
1268
+ }
1269
+
1270
+ // Validate secrets
1271
+ match secrets. validate ( ) ? {
1272
+ Ok ( validated_secrets) => {
1273
+ // Store resolved secrets in OnceCell for Nix to use
1274
+ self . secretspec_resolved
1275
+ . set ( validated_secrets. resolved )
1276
+ . map_err ( |_| miette ! ( "Secretspec resolved already set" ) ) ?;
1277
+ }
1278
+ Err ( validation_errors) => {
1279
+ bail ! (
1280
+ "Required secrets are missing: {} (provider: {}, profile: {})" ,
1281
+ validation_errors. missing_required. join( ", " ) ,
1282
+ validation_errors. provider,
1283
+ validation_errors. profile
1284
+ ) ;
1285
+ }
1286
+ }
1287
+ }
1288
+ }
1289
+
1204
1290
// Create cli-options.nix if there are CLI options
1205
1291
if !self . global_options . option . is_empty ( ) {
1206
1292
let mut cli_options = String :: from ( "{ pkgs, lib, config, ... }: {\n " ) ;
0 commit comments