Skip to content
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

Support CLI arguments for cfn init #574

Merged
merged 13 commits into from
Oct 2, 2020
Merged

Conversation

chuchocd85
Copy link
Contributor

Issue #, if available:
#410

Description of changes:
Add the ability to pass as CLI arguments all the required values to create projects on all the three public language plugins. These new arguments are:

  • --language
  • --type-name
  • --use-docker
  • --namespace
  • --codegen-model
  • --import-path

Ideally this PR should be merged (or at least the changes. released) after #121, #309 and #168.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Copy link
Contributor

@johnttompkins johnttompkins left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only thing that I am hesitant about this approach is that running cfn init --help will produce a lot very verbose set all arguments used across plugins. I know we kind of went over this previously, but maybe it is possible to add a unique parser for each plugin, then surface this using the plugin registry?

Though this may just be something we look at down the road. Other than that the current approach looks good, just want someone else to take a look at it.

from functools import wraps

from colorama import Fore, Style

from .exceptions import WizardAbortError, WizardValidationError
from .plugin_registry import PLUGIN_CHOICES
from .plugin_registry import get_plugin_choices
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason why we are adding this method in? seems to be the same value as PLUGIN_CHOICES

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For testing we add a dummy plugin on the fly but at that point in time PLUGIN_CHOICES is already a constant that does not includes it, making it a method allows to get the latest installed plugins.

print(
Style.BRIGHT,
Fore.RED,
"The plugin for {} is not installed.".format(language),
Copy link
Contributor

@johnttompkins johnttompkins Sep 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"The plugin for {} is not installed.".format(language),
"The plugin for {} cannot be found.".format(language),

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we probably should not always print colorama otherwise it will be really hard to maintain;
maybe we can use for an abstract exception which all will change color entry

{
"use_docker": args.use_docker,
"namespace": args.namespace,
"codegen_template_path": args.codegen_model,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

codegen-model is specific to java plugin and not supported by any other ones; will this fail project init if it's provided? if not we should and assert that it's not supported by the plugin;

Copy link
Contributor Author

@chuchocd85 chuchocd85 Sep 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, each plugin looks for the arguments it needs in project.settings, the fact that additional un-related information is also coming in does not affects the plugin. We sure can do some validation and only allow arguments related to the selected plugin but I don't know if is worth doing that validation or how it will impact the user experience, if the user passes a wrong flag s/he would need to try again (kind of defeating the purpose of being able to use cfn init as just a one-liner). What do you think?

try:
type_name = validate_type_name(args.type_name)
except WizardValidationError as error:
print(Style.BRIGHT, Fore.RED, str(error), Style.RESET_ALL, sep="")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we do it a bit more generic and reusable?

print(
Style.BRIGHT,
Fore.RED,
"The plugin for {} is not installed.".format(language),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we probably should not always print colorama otherwise it will be really hard to maintain;
maybe we can use for an abstract exception which all will change color entry

Comment on lines 199 to 229
"-f",
"--force",
action="store_true",
help="Force files to be overwritten.",
)

parser.add_argument(
"-l",
"--language",
help="""Select a language for code generation.
The language plugin needs to be installed.""",
)

parser.add_argument(
"-t",
"--type-name",
help="Select the name of the resource type.",
)

parser.add_argument(
"-d",
"--use-docker",
action="store_true",
help="""Use docker for python platform-independent packaging.
This is highly recommended unless you are experienced
with cross-platform Python packaging.""",
)

parser.add_argument(
"-n",
"--namespace",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jotompki do you think we could use something like a config file to specify all the metadata - ConfigArgParse is one of the options

Comment on lines +10 to +13
plugin_choices = [
entry_point.name
for entry_point in pkg_resources.iter_entry_points("rpdk.v1.languages")
]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use set if you dont rely on element position

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before my changes the list of plugins was already being sorted which converts the set back to a list. I f this is not required anymore I'll be ok with making it a set.

Copy link
Contributor

@johnttompkins johnttompkins left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall logic looks good i just think the get_args method is being abused a bit to keep track of testing values

args = get_args()
mock_project, patch_project = get_mock_project()

patch_tn = patch("rpdk.core.init.input_typename")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do these need to be patched if we are going the non-interactive route?

mock_project, patch_project = get_mock_project()

patch_tn = patch("rpdk.core.init.input_typename")
patch_l = patch("rpdk.core.init.input_language")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here. no reason to patch if we are just asserting we aren't calling

tests/utils.py Outdated
None if interactive else ("dummy" if language else "invalid_language")
)
args.type_name = (
None if interactive else ("Test::Test::Test" if type_name else "Test")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's just pass in a type name into this get_args util instead of passing in boolean that sets between two values. makes the function more straightforward

tests/utils.py Outdated
)

args.language = (
None if interactive else ("dummy" if language else "invalid_language")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here as below. no need to obfuscate what is going on

mock_project.init.assert_called_once_with(
args.type_name,
args.language,
args.settings,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usually args doesn't have a settings member, correct? let's keep args as simple as possible and in line with what the cli expects its structure to be. it looks like here it is only used to keep track of test values. can always pull that logic out of the get_args method and inline it here.

mock_project.load_settings.side_effect = FileNotFoundError
mock_project.settings_path = ""
mock_project.root = Path(".")
args = get_args(interactive=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this even being used in this test other than the settings?

mock_project.generate.assert_called_once_with()


def test_init_method_noninteractive_invalid_type_name():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

confused on this. how does init reach project init method if the typename is invalid?

@johnttompkins johnttompkins merged commit d14467e into master Oct 2, 2020
@johnttompkins johnttompkins deleted the feature/init-arguments branch October 2, 2020 18:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants