1+ const fs = require ( 'fs' ) ;
2+ const parseArgs = require ( 'yargs-parser' ) ;
3+ const OPTION_PARAMETER_FILE = 'parameter-file' ;
4+ const OPTION_PARAMETER_OVERRIDES = 'parameter-overrides' ;
5+
6+ module . exports = class CfParametersPlugin {
7+ constructor ( serverless , options ) {
8+ console . log ( "Dynamic CF Parameters and Secrets Plugin Initialized" ) ;
9+ this . serverless = serverless ;
10+ this . options = options ;
11+
12+ this . commands = {
13+ deploy : {
14+ options : {
15+ [ OPTION_PARAMETER_FILE ] : {
16+ usage : 'Provide a JSON file to define secrets and parameters' ,
17+ required : true ,
18+ type : 'string'
19+ } ,
20+ [ OPTION_PARAMETER_OVERRIDES ] : {
21+ usage : 'Override parameters dynamically' ,
22+ required : false ,
23+ type : 'multiple'
24+ }
25+ }
26+ } ,
27+ package : {
28+ options : {
29+ [ OPTION_PARAMETER_FILE ] : {
30+ usage : 'Provide a JSON file for parameter overrides' ,
31+ required : false ,
32+ type : 'string'
33+ }
34+ }
35+ }
36+ } ;
37+ this . hooks = {
38+ 'before:package:finalize' : this . addSecretsAndParameters . bind ( this ) ,
39+ 'before:aws:deploy:deploy:updateStack' : this . injectParameterOverrides . bind ( this )
40+ } ;
41+ this . providerRequest = this . getProvider ( ) . request . bind ( this . provider ) ;
42+
43+ }
44+
45+ addSecretsAndParameters ( ) {
46+ const parameterFile = this . options [ OPTION_PARAMETER_FILE ] ;
47+ if ( ! parameterFile ) {
48+ throw new Error ( "The --parameter-file option is required to define secrets and parameters." ) ;
49+ }
50+
51+ const secretsData = JSON . parse ( fs . readFileSync ( parameterFile , 'utf-8' ) ) ;
52+ const cloudFormationTemplate = this . serverless . service . provider . compiledCloudFormationTemplate ;
53+
54+ // console.log("Loaded Secrets Data:", JSON.stringify(secretsData, null, 2));
55+
56+ // Add Parameters
57+ // Note: Here need to enhancement to check if same name of other resource or paramteres exist in stack or not
58+ cloudFormationTemplate . Parameters = {
59+ ...( cloudFormationTemplate . Parameters || { } ) ,
60+ ...this . generateParameters ( secretsData )
61+ } ;
62+
63+ // console.log("Generated Parameters:", JSON.stringify(cloudFormationTemplate.Parameters, null, 2));
64+
65+ // Add Secrets Manager Resources
66+ // Note: Here need to enhancement to check if same name of other resource or secret manager exist in stack or not
67+ cloudFormationTemplate . Resources = {
68+ ...( cloudFormationTemplate . Resources || { } ) ,
69+ ...this . generateSecrets ( secretsData )
70+ } ;
71+
72+ // console.log("Generated Secrets Manager Resources:", JSON.stringify(cloudFormationTemplate.Resources, null, 2));
73+ }
74+
75+ generateParameters ( secretsData ) {
76+ const parameters = { } ;
77+
78+ for ( const [ key ] of Object . entries ( secretsData ) ) {
79+ parameters [ key ] = {
80+ Type : 'String' ,
81+ NoEcho : true
82+ } ;
83+ }
84+
85+ return parameters ;
86+ }
87+
88+ generateSecrets ( secretsData ) {
89+ const resources = { } ;
90+
91+ for ( const [ key ] of Object . entries ( secretsData ) ) {
92+ resources [ `SecretFor${ key } ` ] = {
93+ Type : 'AWS::SecretsManager::Secret' ,
94+ Properties : {
95+ Name : key ,
96+ Description : `Secret dynamically created for ${ key } ` ,
97+ SecretString : {
98+ Ref : key
99+ }
100+ }
101+ } ;
102+ }
103+
104+ return resources ;
105+ }
106+ getProvider ( ) {
107+ if ( ! this . provider ) {
108+ this . provider = this . serverless . getProvider ( 'aws' ) ;
109+ if ( ! this . provider ) {
110+ throw new Error ( "AWS provider not found. Ensure this plugin is used with the AWS provider." ) ;
111+ }
112+ }
113+ return this . provider ;
114+ }
115+ injectParameterOverrides ( ) {
116+
117+ this . provider . request = async ( ...args ) => {
118+ let [ service , method , params ] = args ;
119+
120+
121+ const compiledParametersTemplate =
122+ this . serverless . service . provider . compiledCloudFormationTemplate . Parameters || { } ;
123+ // console.log("compiledParametersTemplate:", compiledParametersTemplate);
124+
125+ if ( service === 'CloudFormation' && ( method === 'updateStack' || method === 'createChangeSet' ) ) {
126+ // Fetch current parameters from the deployed template
127+ const response = await this . providerRequest ( 'CloudFormation' , 'getTemplate' , {
128+ StackName : params . StackName
129+ } ) ;
130+ const currentParameters = JSON . parse ( response . TemplateBody ) . Parameters || { } ;
131+ // console.log("currentParameters:", JSON.stringify(currentParameters));
132+
133+ // Build the list of parameters
134+ const overrides = this . getOverrides ( ) ;
135+ // console.log("overrides:", JSON.stringify(overrides));
136+
137+ params . Parameters = Object . keys ( compiledParametersTemplate )
138+ . map ( ( paramKey ) => {
139+ if ( overrides [ paramKey ] !== undefined ) {
140+ return {
141+ ParameterKey : paramKey ,
142+ ParameterValue : overrides [ paramKey ]
143+ } ;
144+ } else if ( currentParameters [ paramKey ] ) {
145+ return {
146+ ParameterKey : paramKey ,
147+ UsePreviousValue : true
148+ } ;
149+ }
150+ } )
151+ . filter ( Boolean ) ;
152+
153+ // console.log("Final Parameters:", JSON.stringify(params.Parameters));
154+ }
155+
156+ return this . providerRequest ( ...args ) ;
157+ } ;
158+ }
159+
160+
161+
162+
163+ getOverrides ( ) {
164+ let parameterOverrides = this . options [ OPTION_PARAMETER_OVERRIDES ] || [ ] ;
165+ if ( ! Array . isArray ( parameterOverrides ) ) {
166+ parameterOverrides = [ parameterOverrides ] ;
167+ }
168+
169+ const cliOverrides = parseArgs (
170+ parameterOverrides . map ( ( param ) => `--${ param } ` ) . join ( ' ' ) ,
171+ { configuration : { 'parse-numbers' : false } }
172+ ) ;
173+ // console.log("Parameter Overrides from CLI:", JSON.stringify(cliOverrides));
174+
175+ const fileOverrides = this . options [ OPTION_PARAMETER_FILE ]
176+ ? JSON . parse ( fs . readFileSync ( this . options [ OPTION_PARAMETER_FILE ] , 'utf-8' ) )
177+ : { } ;
178+
179+ // console.log("Parameter Overrides from File:", JSON.stringify(fileOverrides));
180+
181+ return { ...fileOverrides , ...cliOverrides } ;
182+ }
183+ } ;
0 commit comments