-
Notifications
You must be signed in to change notification settings - Fork 31
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
Feature/conditionals #362
Feature/conditionals #362
Conversation
7fd5ac0
to
49b44cc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seeing a lot of concatenation of fmt.Sprintf calls in general. How come?
depending on parameters passed to coverge at execution time, information present | ||
about the system during run time, or the status of other processes that have | ||
executed. If you are already familiar with `switch` statements from other | ||
languages you may wish to skip ahead to the section on *Rules of Conditionals* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hugo generates IDs on headers. This one would be #rules-of-conditionals
I think. You could just link to it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh okay, I was trying to figure out how to do that.
To understand how this works, let's consider the following example: You wish to | ||
create a file, `greeting.txt` that contains a greeting like `hello`, however you | ||
want to be aware of the users preferred language so you would like to ensure | ||
that the content of the greeting file is in an appropriate language. Let's look |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this sentence has three verbs. So… three SVO sentences?
content = "hello\n" | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's this HCL ran through converge fmt
:
param "lang" {
default = ""
}
switch "test-switch" {
case "eq `spanish` `{{param `lang`}}`" "spanish" {
file.content "foo-file" {
destination = "greeting.txt"
content = "hola\n"
}
}
case "eq `french` `{{param `lang`}}`" "french" {
file.content "foo-file" {
destination = "greeting.txt"
content = "salut\n"
}
}
case "eq `japanese` `{{param `lang`}}`" "japanese" {
file.content "foo-file" {
destination = "greeting.txt"
content = "もしもし\n"
}
}
default {
file.content "foo-file" {
destination = "greeting.txt"
content = "hello\n"
}
}
}
|
||
`converge graph --local helloLanguages.hcl | dot -Tpng -o hello-languages.png` | ||
|
||
{{< figure src="/images/getting-started/hello-languages.png" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this file is too small to be useful, at least on my screen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, I'll pull a couple of conditions out to simplify
*macro expressions* in the graph, denoted by their names. Each of these nodes | ||
represent a potential runnable action on the system. The determination of what, | ||
if any, of these actions will be run is dependent on the results of branch | ||
evaluation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is phrased from the point of view of the implementer. Could you please reword it (and the following paragraph) in terms of usage instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah that's a good point.
return err | ||
} | ||
|
||
out.Add(id, preparer) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why are you removing spacer lines like these? There are a bunch of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not really on purpose I just have a bad habit of compulsively removing vertical space, I'll try to not do that so much anymore.
if n.IsCase() && len(n.Keys) == 3 { | ||
break | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this area of the code is starting to feel like we should be using an enum and validating length from type, instead of the other way around.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this might be something to open an issue about, though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I was going to change and it and realized it was a bit too tangential to justify as part of this change set. I'll add an issue.
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package control |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this under render? Shouldn't it be like parse/preprocess/switch
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should probably be moved. I had preprocessor under render for the lookup stuff and just kind of mentally complexted the two types of preprocessing.
// GenerateNode generates a parse.Node for the macro-expanded placeholde from | ||
// the case clause | ||
func (c *Case) GenerateNode() (*parse.Node, error) { | ||
switchHCL := fmt.Sprintf("macro.case %q {\n", c.Name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is even happening here. Why are there so many sprintfs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating an ast.Node
from scratch was a lot more fiddly than I anticipated, and it made sense for the moment to just go ahead and sprintf
the hcl and load it. I think we should have a separate issue to refactor, this was just for the first pass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant more that there should probably be one sprintf rather than appending multiple together. Go's multiline strings can help a lot here. Or templates!
|
||
func (c *CaseTask) String() string { | ||
var out string | ||
if nil == c { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this flipped?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bad habit from my C days...
The heavy use of sprintf is that creating an |
} | ||
|
||
// GetParentID uses edge traversal to lookup the parent of the current node. | ||
func (g *Graph) GetParentID(id string) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it'll make it easier for our changes (mine in #369) to merge if this is (id string, ok bool)
. Explicitly identifies the edge condition, as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah that makes sense, and easy change.
if fst == g.GetParentID(snd) { | ||
return false | ||
} | ||
if AreSiblingIDs(fst, snd) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is still doing the calculation based on IDs. Could you change it to use only the metadata in the graph, or would that be too much?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I can do that, it was just easier to re-use the logic from GetParentID
and I thought that would be sufficient since GetParentID
is only looking at the edges and not doing string calculations so we're only really using the string value for the terminal case.
// expandSwitchMacro is responsible for adding the generated switch nodes into | ||
// the graph. Nodes inside of the switch macro are added as children to the | ||
// case statements, who are parents of the outer switch statement. Actual node | ||
// generation happens in parse/preprocessor/switch/ and we add the nodes into |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: trailing slash
// GenerateNode generates a parse.Node for the macro-expanded placeholde from | ||
// the case clause | ||
func (c *Case) GenerateNode() (*parse.Node, error) { | ||
switchHCL := fmt.Sprintf("macro.case %q {\n", c.Name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant more that there should probably be one sprintf rather than appending multiple together. Go's multiline strings can help a lot here. Or templates!
Converge supports the ability to conditionally execute a set of actions | ||
depending on the value of expressions that are evaluated at runtime. These | ||
`switch` expressions will allow you to write a single converge file that will | ||
execute differenting depending on information such as: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
differently
- the status of another resource with `lookup` | ||
|
||
To understand how this works, let's consider the following example: You wish to | ||
create a file, `greeting.txt`. You want that file to contain a greeting like in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need "like" here?
7564005
to
8bb1303
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for the most part, just found a few misspellings in comments. also looks like it is out of date with master; otherwise, lgtm.
sorry for the mix of single comments and review. still getting used to the review feature.
@@ -85,16 +85,46 @@ func (g *Graph) Get(id string) interface{} { | |||
|
|||
// GetParent returns the direct parent vertex of the current node. | |||
func (g *Graph) GetParent(id string) interface{} { | |||
parentID, _ := g.GetParentID(id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure if this is a problem or not but it jumped out at me. will this (and similarly graph.Get
) just end up returning nil if false is returned from GetParentID
?
@@ -148,6 +149,50 @@ func TestDescendents(t *testing.T) { | |||
assert.Equal(t, []string{"one/two"}, g.Descendents("one")) | |||
} | |||
|
|||
// TestChildren tests to ensure the correct behavior when getting children | |||
func TestChildren(t *testing.T) { | |||
defer logging.HideLogs(t)() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add t.Parallel()
?
@@ -87,6 +87,38 @@ func TestNodeValidateTooManyKeysModule(t *testing.T) { | |||
validateTable(t, `module x y z {}`, "1:1: too many keys") | |||
} | |||
|
|||
// TestNodeCase tests various scenarios where the node is a case statement | |||
func TestNodeCase(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we want t.Parallel()
in these tests?
InnerNodes []*parse.Node | ||
} | ||
|
||
// GenerateNode generates a parse.Node for the macro-expanded placeholde from |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
placeholder
"github.com/asteris-llc/converge/resource" | ||
) | ||
|
||
// SwitchPreparer represents a switch resourc; the task it generates simply |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resource
return &resource.Status{}, nil | ||
} | ||
|
||
func (s *SwitchTask) String() string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needs comment?
) | ||
|
||
// ResolveConditionals will walk the graph and wrap tasks whose parent is a case | ||
// in a conditional resource. For cases it will look at the parent switch and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and...?
if c == nil { | ||
return "<nil>" | ||
} | ||
out += fmt.Sprintf("Case: \n") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would a multiline string be more readable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed here, and in the cases elsewhere where there are multiline strings with multiple calls to fmt.Sprintf
8bb1303
to
8d21fca
Compare
Conditional evaluation is working with the following caveats: * Lookups between inner/outer modules do not work * All cases with true predicates are evaluated * Unevaluated branches are not fully prepared * Error messages are a little broken
Need to fix graph traversal for render so that when dependent niblings from switch macros are expanded they have a valid parent type for thunk generation.
This reverts commit 3f2d99c.
e343bef
to
0bbc6e0
Compare
lgtm on green |
Support
switch
conditionals withcase
branches as per #268