New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Seclude CLI params and service config resolution from the core #8364
Comments
Hello @medikoo 👋 I've started thinking about this refactoring, starting with the very first step:
Thanks in advance 🙇 |
@pgrzesik great thanks for asking. This issue was not specified in deep detail, as we planned to handled that internally (intentionally I didn't put "help wanted" label at ths point), as some parts are challenging, and it needs to be done carefuly with other aspects in mind (like future components adaptation) I've also actually also started work on my side on that (but didn't place PR yet) :) Anyway let me answer your questions:
It's about covering most of Framework CLI logic with async try/catch clause (yes, as you show in your snippet) and handle any error with unified handler.
I think I envisioned a simpler output, but as I was implementing this, I didn't go any further than refactoring this logic so it works as standalone. I guess in this initial phase, there's not much else to simplify (probably later when will want to cover also Components with same handler, some output will have to be updated)
At this point I would leave it working same way. I would just not mute original error if handler crashes on it's own (I don't think that current logic is right) |
Thanks for a comprehensive explanation @medikoo and sorry - I've missed the fact that this ticket didn't have "help wanted" label 🙇 |
Ah also, if |
Technically it's more that all properties are resolved at once, and if property which is behind variable depends on the other behind variable, then the other is ensured to be fully resolved before we attempt to resolve first one. |
Hi @medikoo (cc @pgrzesik). If you have a chance, I would appreciate your feedback on doing something like this to support more complicated
I think this is the best way forward into |
I'm not sure if this should go in its own issue, but thoughts on configs.js module.exports = async ({ options, resolveConfigurationProperty }) => {
const region = options.region
|| process.env.REGION
|| 'us-east-1;
...
} .env
). |
@neverendingqs I believe we fixed issue you were confused by, at #9110 In general there's no order in which variables are resolved. All are resolved at once, and if depedency is detected, the dependent waits for dependency. In case were you were approaching error, problem was that at initial resolution phase we assume env variables not being fulfilled (due to eventual |
Thanks! I understand what is happening better now, but am still unsure how the example I provided above will resolve with the changes in #9110. If there are no Having it call .env
configs.js module.exports = async ({ options, resolveConfigurationProperty }) => {
const region = options.region // Not provided in `options`
|| process.env.REGION // Defined in `.env`
|| 'us-east-1'; // Default if not found in either `options` or `.env`
// Convert an env var CSV string into an array before returning
const statements = (process.env.ALLOWED_PRINCIPALS || []).map(...);
...
} |
AWS Region in a framework is resolved as:
No environment variable influences that resolution. Still you've pointed one aspect that's currently missed when JS function resolvers are in place. Indeed when such resolver is invoked first time, I've proposed on how to solve it at #9112 and it's not challenging to do. We will implement that shortly, as definitely it's needed to consider JS resolvers functionality complete. |
@neverendingqs experience with JS function resolvers should be improved with next version, as we've just merged this: #9140 |
Thanks! it looks like
Is this because we need to resolve Could we avoid a resolution of |
Yes, and we need to resolve stage (which depends on
Why? What exactly problem will it solve? Note that |
Using the below as an example: module.exports = async ({ options, resolveConfigurationProperty }) => {
// Convert an env var CSV string into an array of e.g. IAM statements before returning
const statements = (process.env.ALLOWED_PRINCIPALS || [])
.split(',')
.map(principal => ({ ... } );
return { statements };
} If { statements: [] } today. This means we can't use different Note: this is just a use case I would be interested in, but totally understandable if it's not one that will be supported. |
@neverendingqs with next release JS function resolver will be invoked before And in case JS function resolver plays part of resolution of |
Use case description
Ideally it should be straightforward to use Serverless Framework programmatically. Additionally clean separation of concerns will help significantly the maintenance and will allow to integrate Components engine into the repository.
Improvements towards separation of Packaging and Deployment phases has been moved to #8499
Proposed solution
Step by step let's seclude CLI params and service config resolution logic from a core and layout a new processing flow as follows:
-v
, output version info and abort-c, --config
CLI params), into plain not normalized in any way JSON structure). (If there's parsing error and it's not a CLI help request, crash with meaningful error)file
,self
andstrToBool
variable sources (but only those not depending on other resolvers)provider
and eventualprovider.stage
properties are fully resolved. (If there's a validation error and it's not a CLI help request, crash with meaningful error onprovider
being not resolved. Show deprecation and warning notice onprovider.stage
not being resolved).env
files (If there's parsing error and it's not a CLI help request, crash with meaningful error)env
and remainingfile
,self
andstrToBool
variable sources (but only those not depending on other resolvers)plugins
property is fully resolved.(If there's initialization error and it's not a CLI help request, crash with meaningful error)
Additionally to have Framework CLI totally programatic, #1720 has to be addressed.
Implementation spec
Preliminary notes:
runServerless
variant (assuming they're applicable for that)test/unit
folder--verbose
option and improve general help output0.1 Unify process error handling
Error
class (but presents simplified logic due to more generic approach). Place handler logic inlib/cli/handle-error.js
(and ensure some tests for it)uncaughtExceptions
are handled with same error handlerlogError
utility0.2 Generalize process execution span promise handling (as currently served by
serverless.onExitPromise
)try/catch
clause in promise accessible atlib/cli/execution-span.js
(module should export an unresolved promise and method (to be used internally) for resolving it)lib/cli/execution-span.js
promise toserverless.executionSpan
(as it can be valuable for a plugins). Remove it's so far counterpartserverless.onExitPromise
and in places it was used, refer to either promise exported bylib/cli/execution-span.js
orserverless.executionSpan
analytics.sendPending()
to top of the try/catch clause1.0 Seclude
-v, --version
CLI params handlingsls -v [...]
orsls --version [...]
Generate simple version output which follows one implemented internally inCLI
class. Place handling logic inlib/cli/eventually-output-version-info.js
(and ensure some tests for it)-v, --version
option handling and recognition from CLI.js class2.0 Resolve eventual service config file path (service context)
lib/cli/resolve-service-config-path.js
and resemble logic we have now here:serverless/lib/utils/getServerlessConfigFile.js
Lines 13 to 48 in bde334c
Serverless
constructor onconfig.serviceConfigPath
and assign it toserviceConfigPath
propertyconfig.servicePath
fromserviceConfigPath
findServicePath
utilgetConfigFilePath
usage withresolveServiceConfigPath
at interactive CLI setupgetServerlessConfigFilePath
usage withserverless.serviceConfigPath
and remove this util entirely (in interactive CLI setup simply overrideserverless.serviceConfigPath
instead)getConfigFilePath
2.1 Parse service config file source
lib/service-config/read-source.js
, and resemble logic we have here:serverless/lib/utils/getServerlessConfigFile.js
Lines 112 to 127 in bde334c
readServiceConfigSource
crashes expose the error only if it's not CLI help request, otherwise behave as we're not in service context.Serverless
constructor onconfig.serviceConfigSource
and assign it toserviceConfigSource
propertyServervless.js
class methods, replacethis.pluginManager.serverlessConfigFile
references withthis.serviceConfigSource
serverless.serviceConfigSource
with help ofreadServiceConfigSource
module.pluginManager.loadConfigFile
method andpluginManager.serverlessConfigFile
property2.2 Initial (partial) variables resolution
For that we would need to Implement new variables resolver with following modules:
lib/variables/resolve-variables-map.js
Function that takes
serviceConfig
as an input. Traverses it's properties and returns map of all properties which use variable syntax. Result map should expose all information needed for complete variables resolution without a need of repeated property parsing.After we will fully override variable resolution that's currently in a framework (point 5.2), function should be configured to also override all
serviceConfig
properties which depend on variables withnull
values (technically we move all information to variables map, and remove it fromserviceConfig
. It's to ensure that eventual furtherserviceConfig
processing in case of variable resolution errors is not affected by unresolved properties content)Expected format of result map
Note: In case of resolution from external files, new added content will need to have eventual variables resolved through same util
lib/variables/resolve-variables.js
Function that takes
serviceConfig
,variablesMap
andvariablesResolvers
as an input.variablesResolvers
is expected to be a simple map with source type as a key (e.g.self,
fileetc.) and function that takes
serviceConfig` and eeventual param configured for resolver as arguments. Function may return result sync way or async via returned promiseThere should be attempt to resolve every property.
serviceConfig
object and variable reference removed fromvariablesMap
.variablesMap
(in future processing, resolution should be reattempted only if fail was caused by A error, in other cases there should be no retry.If there's any fail. Function crashes, and on it's error it should expose
errorneusVariableKeys
property with keys to each variable resolutions that failed.Having above:
file
,self
andstrToBool
variable sources (but only those not depending on other resolvers). If it fails ignore any not supported source errors. If there are other errors and it's not a CLI help request, in initial stage, ignore them, but after addressing 5.2 signal them with warning message and show a deprecation that with next major we will fail.2.3 Ensure
provider
andprovider.stage
properties are resolved.provider
property still depends on variable resolution, crash with meaningful error, that we cannot accept given form of configurationprovider.stage
property still depends on variable resolution. Show warning and deprecation, stating that it's not recommend to use variables at this property and that we will fail on that with next major2.4 Ensure to load env variables from
.env
filesFollow up with resolution of environment variables from
.env
files (currently being implemented at #8413)2.5 Further (partial) variables resolution
As in 2.1 step, attempt to resolve
file
,self
,strToBool
andenv
variable sources (but only those not depending on other resolvers). If it fails ignore any not supported source errors. If there are other errors and it's not a CLI help request in initial stage, ignore them, but after addressing 5.2 signal them with warning message and show a deprecation that with next major we will fail.2.6 Ensure eventual
plugins
property is fully resolved.Inspect variables map, if
plugins
property still depends on variable resolution, crash with meaningful error, that we cannot accept given form of configuration2.7.0 Recognize help command
lib/cli/is-help-command.js
(it should followcli.isHelpRequest
logic but also recognize--help-components
) and adapt it in internals:isHelpCommand
and pass it toServerless
constructor inconfig.isHelpCommand
and internally assign it toisHelpCommand
propertyserverless.cli.isHelpRequest
usage withserverless.isHelpCommand
pluginManager.cliOptions.help
usage withserverless.isHelpCommand
serverless.cli.isHelpRequest
implementation2.7.1 Recognize commands which are independent of external plugins
Handling of those commands ideally should be totally secluded from Framework engine, still to not impose too timetaking refactor at this step let's simply mark them, to make further processing possible (having that addressed, let's open an issue calling for their seclusion)
plugin
,login
,logout
ordashboard
pass toServerless
constructor ashouldMutePluginInitializationErrors: true
option, and internally assign t to_shouldMutePluginInitializationErrors
propertypluginManager.resolveServicePlugins()
Rely onserverles._shouldMutePluginInitializationErrors
and removepluginManager.pluginIndependentCommands
property.2.7.2 Initialize
Serverless
instance(this will most likely lay out naturally and should not require any code changes)
Follow up with construction of
Serverless
instance and invoke ofserverless.init()
3.0 If CLI help command show help and abort
lib/cli/help/options.js
. It should take our common command configuration object, and resemble logic we have atcli.displayCommandOptions()
lib/cli/help/interactive.js
. It should be a function that accepts an interactiveCLI command configuration and resembles logic we have at `cli.generateInteactiveCliHelp()lib/cli/help/framework.js
. It should be a function that accepts aloadedPlugins
and resembles logic we have atcli.generateMainHelp()
(note we should have CLI: Remove help --verbose option and improve general help output #8497 addressed at this point)lib/cli/help/command.js
. It should be a function that acceptscommandName
andcommand
arguments, and:cli.displayCommandUsage()
logic, and refer to already implementedlib/cli/help/options.js
InteractiveCli
plugin, runlib/cli/help/interactive.js
with its comand and abortlib/cli/help/framework.js
withserverless.cli.loadedCommands
serverless.cli.loadedCommands
lib/cli/help/command.js
with resolved commancli.displayHelp
callCLI
class4.0 Parse CLI arguments
resolveCliInput
validateServerlessConfigDependency
andassignDefaultOptions
) as pursued inpluginManager.invoke
are taken care of. Ideally if it's generalized, so can be also used to validate Components CLI inputlib/cli/parse-params.js
commands
andoptions
toserverless.run()
method. In contextserverless.run()
assign those properties onprocessedInput
propertyprocessedInput
, that happens inserverless.init()
. Still let's override thereprocessedInput
with getter that exposes a deprecation message if property is accessed at initialization phase (having that we will remove it next major)initialize
lifecycle hook (it's first lifecycle event propagated unconditionally). Access CLI options fromserverless.processedInput
(and treat it as read only)pluginManager.validateOptions
so it's eventual errors do not refer to CLI params (this method will now be effective only for programmatic usage)pluginManager.validateServerlessConfigDependency
so it's eventual errors do not refer to CLI usage (e.g. we should refer to service context and not to service directory)pluginManger.convertShortcutsIntoOptions
as Framework will already be populated with resolved shortcuts5.1 Resolve variables for all variable sources which do not depend on config properties
As in 2.1 step, attempt to resolve all variable sources which do not depend on config properties.
If it fails ignore any not supported source errors. If there are other errors in initial stage, ignore them, but after addressing 5.2 signal them with warning message and show a deprecation that with next major we will fail.
5.2 Resolve all remaining variables in service config
As in 2.1 step, attempt to resolve all remaining variables.
If it fails signal them with warning message and show a deprecation that with next major we will fail. Additionally:
null
Remove all variable resolution logic from Framework core
6.0 Run lifecycle events for CLI command
(this will most likely lay out naturally and should not require any code changes)
Follow up with
serverless.run()
Progress summary:
test/unit
folder--verbose
option and improve general help output-v
,--version
CLI params handlingThe text was updated successfully, but these errors were encountered: