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

Feature/conditionals #362

Merged
merged 59 commits into from
Oct 14, 2016
Merged

Feature/conditionals #362

merged 59 commits into from
Oct 14, 2016

Conversation

rebeccaskinner
Copy link
Contributor

Support switch conditionals with case branches as per #268

Copy link
Contributor

@BrianHicks BrianHicks left a 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*
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

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"
}
}
}
Copy link
Contributor

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"
Copy link
Contributor

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

Copy link
Contributor Author

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.
Copy link
Contributor

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?

Copy link
Contributor Author

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)

Copy link
Contributor

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.

Copy link
Contributor Author

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
}

Copy link
Contributor

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.

Copy link
Contributor

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

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 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
Copy link
Contributor

@BrianHicks BrianHicks Oct 11, 2016

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?

Copy link
Contributor Author

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)
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

why is this flipped?

Copy link
Contributor Author

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...

@rebeccaskinner
Copy link
Contributor Author

The heavy use of sprintf is that creating an ast.Node is harder than it really ought to be, sprintf-ing the HCL and parsing it was an easy first pass at getting the nodes generated with the idea that we could have a separate issue to refactor and do it without parsing new hcl.

}

// GetParentID uses edge traversal to lookup the parent of the current node.
func (g *Graph) GetParentID(id string) string {
Copy link
Contributor

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.

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 that makes sense, and easy change.

if fst == g.GetParentID(snd) {
return false
}
if AreSiblingIDs(fst, snd) {
Copy link
Contributor

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?

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 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
Copy link
Contributor

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)
Copy link
Contributor

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:
Copy link
Contributor

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
Copy link
Contributor

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?

Copy link
Contributor

@ryane ryane left a 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)
Copy link
Contributor

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)()
Copy link
Contributor

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) {
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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 {
Copy link
Contributor

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
Copy link
Contributor

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")
Copy link
Contributor

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?

Copy link
Contributor

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

@ryane
Copy link
Contributor

ryane commented Oct 14, 2016

lgtm on green

@BrianHicks BrianHicks merged commit 95aec5f into master Oct 14, 2016
@BrianHicks BrianHicks deleted the feature/conditionals branch October 14, 2016 21:00
BrianHicks added a commit that referenced this pull request Dec 22, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants