8000 Refactor output lookup by ejholmes · Pull Request #550 · cloudtools/stacker · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
8000

Refactor output lookup #550

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

Merged
merged 2 commits into from
Mar 10, 2018
Merged

Refactor output lookup #550

merged 2 commits into from
Mar 10, 2018

Conversation

ejholmes
Copy link
Contributor
@ejholmes ejholmes commented Mar 7, 2018

This refactors the mechanics of the ${output} lookup so that it uses the internal stacker.Stack as the lookup point.

With the addition of the DAG, in order to ensure that output lookups didn't involve unnecessary network calls, we changed stacker.actions.build to call provider.set_outputs to store the stacks outputs when the step was completed.

https://github.com/remind101/stacker/blob/c1aad4f8713c64c90abc02e6c10ad2eda1a18c8e/stacker/actions/build.py#L262-L264

The output lookup would then use this cached value, through provider.get_output.

https://github.com/remind101/stacker/blob/c1aad4f8713c64c90abc02e6c10ad2eda1a18c8e/stacker/lookups/handlers/output.py#L44-L49

This was the smallest change that we could make in the DAG branch, while keeping everything fast and functional, but won't work well moving forward as we think about cross account/region support, as well as the possibility of adding new node types to the graph (e.g. hooks).


In this PR, when a stack is "done", we store the outputs on the internal stacker.Stack object. The output plugin looks up the stack object, then returns the output from there. This has a couple of advantages:

  1. It ensures that we're not keeping global state for cached lookups, so we don't have a potential for a race.
  2. It'll work better when we think about cross account/region stacks using profiles. You can think of a scenario where, stacker is managing two stacks, both named "RoleModel" in separate accounts. The output lookup needs to be aware that these nodes in the graph are separate things.

self._stacks.append(stack)
return self._stacks

def get_stack(self, name):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Needs docs.

Also, if you're wondering why this doesn't use get_stacks_dict, that function indexes by stack.fqn, which we don't want. We want to index by the name of the node in the graph.

# from.
handler = partial(output_handler, rfqn=True)

def handler(value, provider=None, context=None, **kwargs):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since the xref and rxref lookups can't re-use the output lookup, I just went ahead and re-implemented them to be more explicit, since they were pretty hard to follow before with all of the function currying.

< 8000 /tr>
raise ValueError('Context is required')

d = deconstruct(value)
stack_fqn = context.get_fqn(d.stack_name)
Copy link
Contributor Author
@ejholmes ejholmes Mar 7, 2018

Choose a reason for hiding this comment

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

The original rxref implementation does the following, instead of using context.get_fqn:

https://github.com/remind101/stacker/blob/c1aad4f8713c64c90abc02e6c10ad2eda1a18c8e/stacker/lookups/handlers/output.py#L30-L35

I'm not sure I understand the original reasoning for that, since it looks identical to what context.get_fqn does, and tests continue to pass. If there is a reason, we need better tests.


def __repr__(self):
return self.fqn

@property
def requires(self):
requires = set([self.context.get_fqn(r) for r in
self.definition.requires or []])
requires = set(self.definition.requires 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.

Technically this is a breaking change, but one that I doubt would effect very many people. requires would no longer support specifying an fqn, instead it would only support using the node name in the graph (same as how the output lookup works).

Copy link
Member

Choose a reason for hiding this comment

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

Can you run this by the folks in the stacker channel, just to make sure we aren't breaking anything for anyone we know of?


output = provider.get_output(stack_fqn, d.output_name)
return output
stack = context.get_stack(d.stack_name)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because I figure this question will get asked, "What happens if get_stack returns None, or outputs haven't been set?":

The graph ensures that that scenario can't happen (remember, the graph is being built out based on the output lookups). If for some reason it did happen, then the step would just blow up, and any downstream steps would be cancelled.

@codecov-io
Copy link
codecov-io commented Mar 9, 2018

Codecov Report

Merging #550 into master will decrease coverage by 0.09%.
The diff coverage is 89.74%.

Impacted file tree graph

@@            Coverage Diff            @@
##           master     #550     +/-   ##
=========================================
- Coverage   87.55%   87.46%   -0.1%     
=========================================
  Files          95       95             
  Lines        6108     6126     +18     
=========================================
+ Hits         5348     5358     +10     
- Misses        760      768      +8
Impacted Files Coverage Δ
stacker/tests/actions/test_destroy.py 100% <ø> (ø) ⬆️
stacker/tests/actions/test_build.py 96.77% <ø> (ø) ⬆️
stacker/tests/test_stack.py 97.91% <ø> (ø) ⬆️
stacker/actions/diff.py 59.71% <0%> (ø) ⬆️
stacker/actions/build.py 92.46% <100%> (ø) ⬆️
stacker/stack.py 86.15% <100%> (+0.43%) ⬆️
stacker/tests/test_variables.py 100% <100%> (ø) ⬆️
stacker/plan.py 90.65% <100%> (-0.55%) ⬇️
stacker/lookups/handlers/output.py 80% <100%> (-1.82%) ⬇️
stacker/exceptions.py 92.17% <100%> (ø) ⬆️
... and 8 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c1aad4f...c8cf7d6. Read the comment docs.

@ejholmes ejholmes force-pushed the node-names branch 3 times, most recently from 35500f6 to ad6979d Compare March 9, 2018 07:53
@ejholmes ejholmes mentioned this pull request Mar 9, 2018
3 tasks
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.

minor comments, looks good

if stack.name == name:
return stack

def get_stacks_dict(self, fqn=True):
Copy link
Member

Choose a reason for hiding this comment

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

It doesn't look like the fqn argument is used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch.

def set_outputs(self, stack_name, stack):
self._outputs[stack_name] = get_output_dict(stack)
return
def get_output_dict(self, stack):
Copy link
Member

Choose a reason for hiding this comment

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

Is this used anywhere?

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, this gets used in the actions to strip outputs from provider_stack. I'd rather providers just return something like a stacker.providers.base.Stack object instead, but that's a bigger refactor.


def __repr__(self):
return self.fqn

@property
def requires(self):
requires = set([self.context.get_fqn(r) for r in
self.definition.requires or []])
requires = set(self.definition.requires or [])
Copy link
Member

Choose a reason for hiding this comment

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

Can you run this by the folks in the stacker channel, just to make sure we aren't breaking anything for anyone we know of?

@ejholmes
Copy link
Contributor Author

Can you run this by the folks in the stacker channel, just to make sure we aren't breaking anything for anyone we know of?

I ran it poll and nobody said they were using this. Makes sense; just using requires is pretty unlikely in itself, and someone going through the effort to do requires: [${namespace}-stack-name] is probably even more unlikely.

@ejholmes ejholmes merged commit b58e01c into master Mar 10, 2018
@acmcelwee
Copy link
Member
acmcelwee commented Mar 12, 2018

@ejholmes re:

just using requires is pretty unlikely in itself

FWIW, we mostly use requires + native export/import over the output/param pattern because of the strong guarantees we get from pushing the dependency information into the CFN domain.

@ejholmes
Copy link
Contributor Author

FWIW, we mostly uses requires + native export/import over the output/param pattern because of the strong guarantees we get from pushing the dependency information into the CFN domain.

Cool, good to know. We don't have any plans to get rid of requires, so you're still good as long as you're using the "short name" (the name of the stack without the namespace added to it).

@phobologic phobologic mentioned this pull request May 26, 2018
phrohdoh pushed a commit to phrohdoh/stacker that referenced this pull request Dec 18, 2018
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.

4 participants
0