66@dataclass
77class GetSecretOptions :
88 env_name : str
9- app_name : str
9+ app_name : Optional [str ] = None
10+ app_id : Optional [str ] = None
1011 key_to_find : Optional [str ] = None
1112 tag : Optional [str ] = None
1213 secret_path : str = "/"
1314
15+ def __post_init__ (self ):
16+ if not self .app_name and not self .app_id :
17+ raise ValueError ("Either app_name or app_id must be provided" )
18+
1419@dataclass
1520class GetAllSecretsOptions :
1621 env_name : str
17- app_name : str
22+ app_name : Optional [str ] = None
23+ app_id : Optional [str ] = None
1824 tag : Optional [str ] = None
1925 secret_path : str = "/"
2026
27+ def __post_init__ (self ):
28+ if not self .app_name and not self .app_id :
29+ raise ValueError ("Either app_name or app_id must be provided" )
30+
2131@dataclass
2232class CreateSecretsOptions :
2333 env_name : str
24- app_name : str
2534 key_value_pairs : List [Dict [str , str ]]
35+ app_name : Optional [str ] = None
36+ app_id : Optional [str ] = None
2637 secret_path : str = "/"
2738
39+ def __post_init__ (self ):
40+ if not self .app_name and not self .app_id :
41+ raise ValueError ("Either app_name or app_id must be provided" )
42+
2843@dataclass
2944class UpdateSecretOptions :
3045 env_name : str
31- app_name : str
3246 key : str
3347 value : Optional [str ] = None
48+ app_name : Optional [str ] = None
49+ app_id : Optional [str ] = None
3450 secret_path : str = "/"
3551 destination_path : Optional [str ] = None
3652 override : bool = False
3753 toggle_override : bool = False
3854
55+ def __post_init__ (self ):
56+ if not self .app_name and not self .app_id :
57+ raise ValueError ("Either app_name or app_id must be provided" )
58+
3959@dataclass
4060class DeleteSecretOptions :
4161 env_name : str
42- app_name : str
4362 key_to_delete : str
63+ app_name : Optional [str ] = None
64+ app_id : Optional [str ] = None
4465 secret_path : str = "/"
4566
67+ def __post_init__ (self ):
68+ if not self .app_name and not self .app_id :
69+ raise ValueError ("Either app_name or app_id must be provided" )
70+
4671@dataclass
4772class PhaseSecret :
4873 key : str
@@ -51,49 +76,127 @@ class PhaseSecret:
5176 path : str = "/"
5277 tags : List [str ] = field (default_factory = list )
5378 overridden : bool = False
79+ application : Optional [str ] = None
80+ environment : Optional [str ] = None
5481
5582class Phase :
5683 def __init__ (self , init = True , pss = None , host = None ):
5784 self ._phase_io = PhaseIO (init = init , pss = pss , host = host )
5885
86+ def _resolve_secret_values (self , secrets : List [PhaseSecret ], env_name : str , app_name : str ) -> List [PhaseSecret ]:
87+ """
88+ Utility function to resolve secret references within secret values.
89+
90+ Args:
91+ secrets (List[PhaseSecret]): List of secrets to process
92+ env_name (str): Environment name for secret resolution
93+ app_name (str): Application name for secret resolution
94+
95+ Returns:
96+ List[PhaseSecret]: List of secrets with resolved values
97+ """
98+ # Convert PhaseSecret objects to dict format expected by resolve_all_secrets
99+ all_secrets = [
100+ {
101+ 'environment' : secret .environment or env_name ,
102+ 'path' : secret .path ,
103+ 'key' : secret .key ,
104+ 'value' : secret .value
105+ }
106+ for secret in secrets
107+ ]
108+
109+ # Create new list of secrets with resolved values
110+ resolved_secrets = []
111+ for secret in secrets :
112+ resolved_value = resolve_all_secrets (
113+ value = secret .value ,
114+ all_secrets = all_secrets ,
115+ phase = self ._phase_io ,
116+ current_application_name = secret .application or app_name ,
117+ current_env_name = secret .environment or env_name
118+ )
119+
120+ resolved_secrets .append (PhaseSecret (
121+ key = secret .key ,
122+ value = resolved_value ,
123+ comment = secret .comment ,
124+ path = secret .path ,
125+ tags = secret .tags ,
126+ overridden = secret .overridden ,
127+ application = secret .application ,
128+ environment = secret .environment
129+ ))
130+
131+ return resolved_secrets
132+
59133 def get_secret (self , options : GetSecretOptions ) -> Optional [PhaseSecret ]:
60134 secrets = self ._phase_io .get (
61135 env_name = options .env_name ,
62136 keys = [options .key_to_find ] if options .key_to_find else None ,
63137 app_name = options .app_name ,
138+ app_id = options .app_id ,
64139 tag = options .tag ,
65140 path = options .secret_path
66141 )
67142 if secrets :
68143 secret = secrets [0 ]
69- return PhaseSecret (
144+ phase_secret = PhaseSecret (
70145 key = secret ['key' ],
71146 value = secret ['value' ],
72147 comment = secret .get ('comment' , '' ),
73148 path = secret .get ('path' , '/' ),
74149 tags = secret .get ('tags' , []),
75- overridden = secret .get ('overridden' , False )
150+ overridden = secret .get ('overridden' , False ),
151+ application = secret .get ('application' ),
152+ environment = secret .get ('environment' )
153+ )
154+
155+ # Resolve any secret references in the value
156+ resolved_secrets = self ._resolve_secret_values (
157+ [phase_secret ],
158+ options .env_name ,
159+ secret .get ('application' , options .app_name )
76160 )
161+
162+ return resolved_secrets [0 ] if resolved_secrets else None
77163 return None
78164
79165 def get_all_secrets (self , options : GetAllSecretsOptions ) -> List [PhaseSecret ]:
80166 secrets = self ._phase_io .get (
81167 env_name = options .env_name ,
82168 app_name = options .app_name ,
169+ app_id = options .app_id ,
83170 tag = options .tag ,
84171 path = options .secret_path
85172 )
86- return [
173+
174+ if not secrets :
175+ return []
176+
177+ # Get the application name from the first secret
178+ app_name = secrets [0 ].get ('application' , options .app_name )
179+
180+ phase_secrets = [
87181 PhaseSecret (
88182 key = secret ['key' ],
89183 value = secret ['value' ],
90184 comment = secret .get ('comment' , '' ),
91185 path = secret .get ('path' , '/' ),
92186 tags = secret .get ('tags' , []),
93- overridden = secret .get ('overridden' , False )
187+ overridden = secret .get ('overridden' , False ),
188+ application = secret .get ('application' ),
189+ environment = secret .get ('environment' )
94190 )
95191 for secret in secrets
96192 ]
193+
194+ # Resolve any secret references in the values
195+ return self ._resolve_secret_values (
196+ phase_secrets ,
197+ options .env_name ,
198+ app_name
199+ )
97200
98201 def create_secrets (self , options : CreateSecretsOptions ) -> str :
99202 # Convert the list of dictionaries to a list of tuples
@@ -103,6 +206,7 @@ def create_secrets(self, options: CreateSecretsOptions) -> str:
103206 key_value_pairs = key_value_tuples ,
104207 env_name = options .env_name ,
105208 app_name = options .app_name ,
209+ app_id = options .app_id ,
106210 path = options .secret_path
107211 )
108212 return "Success" if response .status_code == 200 else f"Error: { response .status_code } "
@@ -113,6 +217,7 @@ def update_secret(self, options: UpdateSecretOptions) -> str:
113217 key = options .key ,
114218 value = options .value ,
115219 app_name = options .app_name ,
220+ app_id = options .app_id ,
116221 source_path = options .secret_path ,
117222 destination_path = options .destination_path ,
118223 override = options .override ,
@@ -124,29 +229,6 @@ def delete_secret(self, options: DeleteSecretOptions) -> List[str]:
124229 env_name = options .env_name ,
125230 keys_to_delete = [options .key_to_delete ],
126231 app_name = options .app_name ,
232+ app_id = options .app_id ,
127233 path = options .secret_path
128234 )
129-
130- def resolve_references (self , secrets : List [PhaseSecret ], env_name : str , app_name : str ) -> List [PhaseSecret ]:
131- all_secrets = [
132- {
133- 'environment' : env_name ,
134- 'application' : app_name ,
135- 'key' : secret .key ,
136- 'value' : secret .value ,
137- 'path' : secret .path
138- }
139- for secret in secrets
140- ]
141-
142- for secret in secrets :
143- resolved_value = resolve_all_secrets (
144- secret .value ,
145- all_secrets ,
146- self ._phase_io ,
147- app_name ,
148- env_name
149- )
150- secret .value = resolved_value
151-
152- return secrets
0 commit comments