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

Formalize the existence of external (read-only) stacks #618

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

danielkza
Copy link
Contributor

Currently, the ref and xref lookups always use the same AWS profile and region that Stacker is initialized with. That means they cannot access outputs in other accounts or regions. It is possible to create a stack that will just "pull" those outputs by adding locked: true to it. But that still does not make it possible to reference a stack outside of the current namespace, if one is configured. Additionally, options that should never take effect for a "read-only" stack were allowed, such as class_path or stack_policy_path.

To handle that use case, introduce and document the external: yes option for stacks. External stacks do not have any of the options related to creating or updating resources, such as class_path and template_path, and are used only as sources of values to other stacks (through outputs and lookups).
Additionally, add an fqn option to external stacks to fix the namespace issue.

Internal changes

To ensure the separation has the desired effect, enable strict mode while parsing configuration into models, but only for non-top-level keys. Achieve it by manually filtering unrecognized top-level keys before forwarding the data for parsing.

To better separate external and managed stacks in config, rework the model hierarchy a little: add BaseStack that only contains the shared config options between both stack types, make it a superclass of Stack, and introduce ExternalStack. Unfortunately Schematics 2.0 has a bug where it did not validate models parsed using polymorphism, so upgrade to version 2.1.

Move decisions on whether a stack needs to be submitted or updated to the stacker.stack.Stack class, to simply the logic and allow it to differ between external and managed stacks. Introduce stacker.stack.ExternalStack with "empty" implementations of all the methods/properties related to templates, blueprints and updates.

Documentation updates

Rework the Stack documentation to list options allowed for all stacks, external and managed stacks separately. Document everything in YAML format, so that people can copy snippets to their configs from the different parts. Fix some typos and clarify some details in the process.

Open questions

The external/managed terminology is the best I could come up with, but I still feel it can be improved. Any suggestions of alternatives are welcome!

Notes

I ran functional tests manually on my all account and they passed - I even had to fix one that had stack_policy_path misspelled and was no longer accepted due to strict parsing.

@danielkza
Copy link
Contributor Author

Should't the unit tests also be running on Python 3 now?

self.external = True

def validate_fqn(self, data, value):
if (not value and not data["stack_name"]) or \
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just noticed this is wrong, since we can infer the CF name from the stack name. Will fix!

enabled=stack_def.enabled,
protected=stack_def.protected,
)
if isinstance(stack_def, ExternalStackModel):
Copy link
Contributor

Choose a reason for hiding this comment

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

I like the change to inject a fake Stack implementation, but I'm wondering if an external flag + config.ExternalStack is overkill. At the moment, a locked stack that has no blueprint or template_path should be considered a read-only stack.

What if the condition here was just:

if stack_def.locked and not stack_def.blueprint and not stack_def.template_path:
  # ExternalStack

That would be a much smaller change and help reduce the size of this PR (and be more backwards compatible with existing configs).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That was my first approach, but I noticed more than half of an external stack's attributes would be completely invalid/useless, and that I needed to add conditions that checked stack.external in dozens of places. Additionally, being loose with parsing can be an easy source of mistakes, so much that one of Stacker's own functional tests had a mistyped key that was never noticed! While there were a significant amount of changes, adding enough tests to the Stack models to cover all cases where external was at would be as big if not bigger.

@danielkza
Copy link
Contributor Author

danielkza commented Jul 7, 2018

Interesting note: I found 20+ usages of invalid options in stacks in my company's repo when using this PR (due to enabling strict parsing). Most of them were harmless (using enable instead of enabled), but a few were a bit more dangerous.

I can make a PR that just enables strict parsing before this one (which is larger and a bit more controversial). What do you think @ejholmes?

edit: PR here: #623


namespace: other-example
stacks:
- name: vpc
Copy link
Member

Choose a reason for hiding this comment

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

maybe throw a comment here stating that this stack won't be changed/build/etc because of the external flag (it is redundant, but I think it's useful)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure.

# dependencies are automatically created when using an output from a stack in
# the variables of another stack. Use it when you know that a hidden dependency
# exists (or one is documented in the CloudFormation reference).
dependencies:
Copy link
Member

Choose a reason for hiding this comment

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

This should be requires, right?

Copy link
Member

Choose a reason for hiding this comment

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

Ping on this one ^^

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed, I missed it :)

@phobologic
Copy link
Member

@ejholmes @danielkza - I'm thinking we should spin a release of stacker this week, it's been quite a while, and we have a lot of features/updates already. Do you think we should wait for this? What's left for discussing here?

@danielkza
Copy link
Contributor Author

I'm looking to revive this PR as I update our internal Stacker fork to the latest master. The strict parsing change has been integrated for a while already, any other concerns I should handle to get this merged? @ejholmes @phobologic

Copy link
Member

@phobologic phobologic left a comment

Choose a reason for hiding this comment

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

Some minor changes, otherwise this looks good to me. Thanks!

docs/config.rst Outdated
Stacks can be classified as:

Managed
Stacks that are fully managed by the current Stacker configuration.
Copy link
Member

Choose a reason for hiding this comment

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

Worth mentioning this is the default?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I can clarify this section a bit!

docs/config.rst Outdated
Stacks that are fully managed by the current Stacker configuration.
They will be created, updated or destroyed as needed.
External
Stacks that will not be modified by the current Stacker configuration.
Copy link
Member

Choose a reason for hiding this comment

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

Might be worth saying that these stacks are configured with the external flag.

# dependencies are automatically created when using an output from a stack in
# the variables of another stack. Use it when you know that a hidden dependency
# exists (or one is documented in the CloudFormation reference).
dependencies:
Copy link
Member

Choose a reason for hiding this comment

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

Ping on this one ^^

def set_outputs(self, outputs):
self.outputs = outputs

def should_submit(self):
Copy link
Member

Choose a reason for hiding this comment

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

What does this end up looking like in the CLI output when we hit an external stack? Does it say it's submitting/creating?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe it prints "skipped". Do you think we should add an additional status to print something different?

Copy link
Member

Choose a reason for hiding this comment

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

Hmm - yeah, that might be useful. It can still be skipped, just with a slightly different message maybe?

tests/suite.bats Outdated
@@ -162,7 +162,7 @@ namespace: ${STACKER_NAMESPACE}
stacks:
- name: vpc
class_path: stacker.tests.fixtures.mock_blueprints.VPC
stack_policy: tests/fixtures/stack_policies/default.json
stack_policy_path: fixtures/stack_policies/default.json
Copy link
Member

Choose a reason for hiding this comment

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

We broke these up into individual files, so you'll probably want to merge master and get that cleared up here.

@danielkza
Copy link
Contributor Author

Rebased to latest master. Was a bit harder than what I hoped, and I ended up having to make some changes to the build action tests.

I'll address your review comments in further commits @phobologic :)

@danielkza
Copy link
Contributor Author

I also found some issues running the functional tests, good thing we have those!

@danielkza
Copy link
Contributor Author

danielkza commented Mar 12, 2019

I'm circling back to this. Rebased to latest master again, but unfortunately I'm seeing failed unit tests locally due to #709.

To import outputs from stacks that are not managed directly by a Stacker
config and require custom settings (such as a profile or region), it was
previously possible to just omit both a `class_path` and
`template_path`.

Make that pattern "official" by introducing an `external` option, and a
simplified `ExternalStack` model that omits all the stack keys that do
not make sense if we're never going to create/update the stack.

Note: the upgrade to schematics 2.1.0 is required due to a bug [1] where
polymorphic models were not properly validated.

[1]: schematics/schematics#440
Don't ever attempt to update them, but always submit them so outputs can
be grabbed.
This makes handling the difference between managed and external stacks
much easier.

Also clean up the build tests a little bit by creating a TestStack class
instead of using Mocks.
Copy link
Member

@phobologic phobologic left a comment

Choose a reason for hiding this comment

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

This looks good @danielkza - can you merge master in (it looks like it's confused about some docs changes), and then we'll get it merged. Thanks!

@danielkza
Copy link
Contributor Author

@phobologic Things looked up-to-date to me, but I took the opportunity to improve the wording on the docs a little bit and forced-pushed.

@phobologic
Copy link
Member

Weird! I wonder why it is showing the additions of stack_name, which is already in the master branch. Ok, once tests pass I'll go ahead and merge :)

@danielkza
Copy link
Contributor Author

I think it's because I moved it around in the file a little bit.

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.

3 participants