nightroman / Invoke-Build

Build Automation in PowerShell

Home Page:https://github.com/nightroman/Invoke-Build/wiki

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Expose current build job in Enter-BuildJob

jfpoirier-x2o opened this issue · comments

We use scriptblock attributes in task definitions to enable easy code reuse, such as

Task dummy {
  [UsesTerraform]
  param()

  ...
}

Enter-BuildJob is the ideal spot to process the scriptblock's attributes and run them, but it needs to be supplied with the current job for this to work. At this time, it is called without passing the current job (stored in ${*j}), and we have to break encapsulation to hijack ${*j} and access the attributes, eg

Enter-BuildJob {
  $currentJob = ${*j}
  foreach($attrib in $currentJob.Attributes) {
    $attrib.Run()   # custom method in script block attributes
  }

It looks like an original and useful technique. I'll take a look indeed.

Out of curiosity, how do you define such custom attributes, right in PowerShell or module or something else?

We define them in Powershell directly, by writing classes that inherit from System.Attribute; Powershell makes them available via properties on the automation scriptblock object, eg.

UsesTerraform.ps1

class UsesTerraform : System.Attribute {
  Run() {
    ...
  }
}

build.ps1

Import-Module UsesTerraform.ps1

Task sometask {
  [UsesTerraform()]
  param()

  ...
}

then Enter-BuildJob as above.

This is really cool. I'd like to play with this and publish the topic in the IB collection https://github.com/nightroman/Invoke-Build/tree/master/Tasks#readme

But at first, implement your request :)

UPDATE: see v5.8.3 below

v5.8.2
Enter-BuildJob and Exit-BuildJob are called with the job script block as the first argument.

I'll add tests and the topic later but the change looks straightforward and reasonable, done.

v5.8.4
Use $Job variable instead of argument.

After some thinking, the public / automatic variable $Job is used instead of argument. This is symmetric with $Task and also makes Enter-BuildJob and Exit-BuildJob slightly simpler, they do not have to declare param or use args[0].

While would I use this over functions? Anything I miss here ? Semantics of invocation is a little bit different but that can be also done with functions and xxx-BuildJob hooks.

task task1 {
	[UsesFoo1()]
	[UsesFoo2()]
	param()
}

vs

task task1 {
       Foo1()
       Foo2()
}

@majkinetor I think in most cases functions will do. But the example I provided here https://github.com/nightroman/Invoke-Build/tree/master/Tasks/Attributes shows that attributes provide more than just actions in the beginning.

In any case exposing $Job is symmetrical to exposing $Task and may be used for attributes scenarios or whatever else.

Our use case is somewhat specific, but this feature is very useful to us: we use Invoke-Build as a software platform, not just a single-project build system. We have built our infrastructure-as-code system around Terraform declarations and Invoke-Build processing. This means we have an ever-growing collection of tasks, as every task wraps a semantic portion of our cloud resources.

As such, attributes allow us to semantically mark every task.

As they are parsed/executed before the task starts, they allow to clearly set the stage before a task runs, and to be combined (important point), which is something functions can't do gracefully (granted, the example above was over-simplified).

As the attributes are parsed at job start time, we can process the entire set and use some of them to affect others.

Think of something like:

sometask {
[UsesTerraform()]
[UsesStorageAccount(xxxx)]
[RequiresTaskTFState(taskname)]
[UsesAzureSDK()]
param()
}

This would do:

  1. setup the Terragrunt/Terraform environment (we use an independent Terraform state-per-task model) by configuring files and directories
  2. alter the previous Terraform requirement to use a different remote state only for this task
  3. import the output Terraform state from another task into our current environment
  4. bring in the required .net modules for Azure SDK, late-bound

THEN the task code starts.

It allows the task code to be free of setup code and to have the context clearly marked, making sure that Terraform folks only concentrate on Terraform code while framework folks code all the required support around them.

@jfpoirier-x2o thanx for detailed explanation, definitely makes much more sense now. Looks like really neat solution.