Using the Terraform (v0.9.6) GitHub Provider, how can one separately assign multiple distinct issue labels to a list of GitHub repositories while using one resource?
With another language I'd probably write something along the lines of:
for i in repos {
for j in tags[i] {
make tag j on repo i
}
}
In this example below I am adding multiple tags to a single repository. The map keys are the repos, and the values are a list of strings.:
variable "issue-labels" {
type = "map"
default = {
"repo_0" = "tag1, tag2, tag3"
"repo_1" = "tag4"
"repo_2" = "tag5, tag6"
}
}
resource "github_issue_label" "issue_labels" {
count = "${length(split(", ", join(", ", lookup(issue-labels, "my-repo"))))}"
repository = "my-repo"
name = "${element(split(", ", join(", ", lookup(issue-labels, "my-repo"))), count.index)}"
color = "FFFFFF"
}
Currently seeking an answer to what feels like an inner loop in terraform. Either finding some way to iterate through the repositories and make multiple resource counts for each, or a workaround involving interpolation to assign the right repo when we iterate through the total number of labels.
Using list variables and other complex structures to "abstract away" resources is not idiomatic and tends to lead to configuration that is hard to read and maintain.
The idiomatic style is to manage repetitive constructs using multiple instances of a child module.
For example, one can make a subdirectory called repository that contains a repository.tf file such as the following:
variable "name" {
}
variable "labels" {
type = "list"
}
# can instead use github_team_repository here if appropriate
resource "github_repository" "this" {
name = "${var.name}"
# ...
}
resource "github_issue_label" "all" {
count = "${length(var.labels)}"
repository = "${var.name}"
name = "${var.labels[count.index]}"
color = "FFFFFF"
}
Now in the root module, rather than using variables to define the set of repositories, one can instead instantiate this resource once for each repository:
module "foo_repo" {
source = "./repository"
name = "foo"
labels = ["tag1", "tag2", "tag3"]
}
module "bar_repo" {
source = "./repository"
name = "bar"
labels = ["tag4"]
}
module "baz_repo" {
source = "./repository"
name = "baz"
labels = ["tag5", "tag6"]
}
With this style, the settings for each repository are kept together in a single module block each, thus making them easy to read and update. Adding a new repository is done by adding a new module block.
This style also allows removing a single repository without disturbing all of the ones after it, since the module states are stored by the module name rather than by index into a a list.
The general recommendation here is that count should be used sparingly, and that it's usually better to write config explicitly rather than generate it via variables. The result is often easier to read and maintain, and easier to adapt over time as requirements change.
Related
I have created sample function here in c# which set the location value based on the parameter. I want to write below expression by using arm template style format.
public static Main(string name)
{
string location = string.Empty;
if(name == "uksouth")
{
location = "UKS";
}else if(name == "ukwest")
{
location = "UKE";
}else if(name == "IndiaWest")
{
location = "INDW";
}
else {
location = "INDS";
}
}
I have written this for one match condition, but i want to return value based on the user resource group.
"value": "[if(equals(resourceGroup().location,'uksouth'), 'UKS', 'EUS')]"
Unfortunately, ARM templates don't provide the equivalent of a "switch" mechanism, which is what might make this easier. However, you can nest multiple if statements. The syntax is a bit clunky, but this should be the equivalent of the code you've written:
"value": "[if(equals(resourceGroup().location,'uksouth'), 'UKS', [if(equals(resourceGroup().location,'ukwest'), 'UKE', [if(equals(resourceGroup().location,'IndiaWest'), 'INDW', 'INDS')])])]"
Here's the same code with a little formatting applied to make it more obvious what's happening here:
"value": "
[if(equals(resourceGroup().location,'uksouth'),
'UKS',
[if(equals(resourceGroup().location,'ukwest'),
'UKE',
[if(equals(resourceGroup().location,'IndiaWest'),
'INDW',
'INDS')])])]
"
You might also consider the approach described in this answer for a bit of a cleaner solution.
Try defining a variable that is an object used like a hashtable. Retrieve different properties from the object by key-name, accessing the properties as key-value pairs. I use something very similar to lookup values inside my ARM templates.
"variables" {
"locationShorten": {
"uksouth": "UKS",
"ukwest": "UKE",
"IndiaWest": "INDW",
"IndiaSouth": "INDS"
},
"locationShort": "[variables('locationShorten')[resourceGroup().location]]"}
Microsoft defines an object's properties as key-value pairs. "Each property in an object consists of key and value. The key and value are enclosed in double quotes and separated by a colon (:)."
Source: https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/data-types#objects
As for the documentation on using [] to access object properties, I can no longer find it for JSON but it is there for BICEP. "You can also use the [] syntax to access a property."
Source: https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/data-types#objects
I am creating a module that has some fairly heavily nested hashes. The hash needs to be semi-regularly modified by the module, which unfortunately rules out using Map.
Generally, a branch of the nested hash will be returned to users of the module [1], and the simplest thing to do is to just return that nested hash, e.g.:
return %data{$branch}{$subbranch}
# ↪︎ %(subsubbranch1 => ... , subsubbranch2 => ... )
However, the nature of containers like arrays or hashes is that while you can make them read-only, the key/values can still be modified. The module users though should not actually modify those values for a number of reasons. Coercing to Map won't help, because if any of the values are also containers, they too will be modifiable.
My first thought was to subclass Hash (or otherwise make a custom Associative), but autovivification by default still goes to Hash. That, however, can be easily solved by overriding both AT-KEY and ASSIGN-KEY so that the AT-KEY returns an instance of the subclass if the key doesn't already exist:
class ProtectedHash is Hash {
has %!hash = ();
method EXISTS-KEY ($key) { %!hash{$key}:exists }
method ASSIGN-KEY ($key, \value) { %!hash{$key} = value }
method AT-KEY ($key) {
%!hash{$key} := ProtectedHash.new unless %!hash{$key}:exists;
%!hash{$key};
}
}
What I'd like to do is to fail if the ASSIGN-KEY (or the autovivification part of AT-KEY) is called from outside my module. I thought about using something like $?MODULE but that would be set at compile time and always be true. It looks like I can shimmy off of Backtrace a bit and check for the name of the file that called, but how consistent can I assume the call trace to those two functions?
For example, for ASSIGN-KEY I've got:
method ASSIGN-KEY ($key, \value) {
my #trace = Backtrace.new.list[3..*];
# The first three can be ignored:
# 0: code at ...Backtrace.pm6
# 1: method new at ...Backtrace.pm6
# 2: method AT-KEY at ...ThisFile.pm6
if/unless ??? {
%!hash{$key} = value
}
}
AT-KEY is normally called by the sub postcircumfix<{ }> (in which case #trace[0] can be ignored, and trace[1] would be the one of interest) but could also be, albeit rarely, called directly, in which case trace[0] is where I'd want to verify the file name.
Are there any other common ways in which AT-KEY or ASSIGN-KEY might be called? Or should check those two steps account for 99.9% of calls to those methods? [2]
[1] There are only a few subx4 branches that a user might want to manipulate, and so I figure it's best to provide them with the necessarily-slower .Hash method for when they really need it than to assume they always need a manipulable container. At times these may be called enough (particularly via a get-branch($foo){$subbranch}{$subsubbranch} pattern), that the addition overhead in creating a deepclone of the Hash becomes decently consequential.
[2] I'm not too concerned about preventing ANY access (although I'm certainly curious if that's possible purely via subclassing), because I'm sure that a fairly industrious coder could always figure something out, but I'd like to catch the most common ones as a way of saying "Can't touch this!" (cue the 90's music…) and provide an Awesome error message.
It's probably easier to achieve this by returning something wrapping the original Array or Hash, or alternatively using but to do a shallow copy and mix in to it (which means you retain the original type).
We can declare a role like this:
role Can'tTouchThis {
method AT-KEY(|) {
untouchable callsame
}
method ASSIGN-KEY(|) {
die "Cannot assign to this";
}
method AT-POS(|) {
untouchable callsame
}
method ASSIGN-POS(|) {
die "Cannot assign to this";
}
}
Where the sub untouchable is defined as:
multi untouchable(Positional \p) {
p but Can'tTouchThis
}
multi untouchable(Associative \a) {
a but Can'tTouchThis
}
multi untouchable(\o) {
o
}
Thus handling nested data structures by - on access - creating a read-only facade to those too.
Here's an example and some test cases to illustrate the effect:
class Example {
has %!foo = a => [ 1, 2, [ 3, 4] ], b => { c => { d => 42, e => 19 }, f => 100 };
method get($sym) {
untouchable %!foo{$sym}
}
}
given Example.new {
use Test;
# Positional cases
is .get('a')[0], 1;
is .get('a')[2][1], 4;
dies-ok { .get('a')[1] = 42 };
is .get('a')[1], 2;
# Associative cases
is .get('b')<c><d>, 42;
dies-ok { .get('b')<f> = 99 };
dies-ok { .get('b')<c><d> = 99 };
is .get('b')<f>, 100;
is .get('b')<c><d>, 42;
# Auto-viv also doesn't work
dies-ok { .get('a')[4]<a> = 99 };
dies-ok { .get('a')[4][0] = 99 };
}
Remove the untouchable call in the get method to see the majority of the tests here fail due to lack of protection.
The solution I ultimately employed served my needs, and I'm posting it here for those who may encounter similar situations. (The answer with role mixing unfortunately doesn't survive binding)
My ultimate approach was to worry the most about unintended editing. To protect against this, I created an Associative-type class called DB-Item that internally has a hash. The AT-KEY method returns the item from the hash if it exists, but ASSIGN-KEY and BIND-KEY simply immediately fail with an appropriate error message. The only other method is ADD-TO-DATABASE. That method handles adds leafs/branches depending on what it's passed (and in general end users should be wary of using all caps methods directly). Since branches can be of different lengths, this also greatly simplifies the initial DB creation:
class DB-Item does Associative {
has %!hash = ();
my $epitaph = "Modification of the database is not a good idea:\n" ~
" - Use .clone if you want to get a editable branch.\n" ~
" - If you really know what you're doing, use .ADD-TO-DATABASE";
method ADD-TO-DATABASE (*#branch) {
if #branch == 2 {
%!hash{#branch.head} = #branch.tail
}else{
%!hash{#branch.head} = DB-Item.new;
%!hash{#branch.head}.ADD-TO-DATABASE(#branch[1..*]);
}
}
method ASSIGN-KEY(|) is hidden-from-backtrace { die $epitaph }
method BIND-KEY(|) is hidden-from-backtrace { die $epitaph }
method EXISTS-KEY($key) { %!hash{$key}:exists }
method AT-KEY($key) { %!hash{$key}:exists ?? %!hash{$key} !! Nil }
method clone { ... }
}
What does it mean in Coffeescript when a variable name begins with an "#" sign?
For example, I've been looking through the hubot source code and just in the first few lines I've looked at, I found
class Brain extends EventEmitter
# Represents somewhat persistent storage for the robot. Extend this.
#
# Returns a new Brain with no external storage.
constructor: (robot) ->
#data =
users: { }
_private: { }
#autoSave = true
robot.on "running", =>
#resetSaveInterval 5
I've seen it several other places, but I haven't been able to guess what it means.
The # symbol is a shorcut for this as you can see in Operators and Aliases.
As a shortcut for this.property, you can use #property.
It basically means that the “#” variables are instance variables of the class, that is, class members. Which souldn't be confused with class variables, that you can compare to static members.
Also, you can think of #variables as the this or self operators of OOP languages, but it's not the exact same thing as the old javascript this. That javascript this refer to the current scope, which causes some problems when your are trying to refer to the class scope inside a callback for example, that's why coffescript have introduced the #variables, to solve this kind of problem.
For example, consider the following code:
Brain.prototype = new EventEmitter();
function Brain(robot){
// Represents somewhat persistent storage for the robot. Extend this.
//
// Returns a new Brain with no external storage.
this.data = {
users: { },
_private: { }
};
this.autoSave = true;
var self = this;
robot.on('running', fucntion myCallback() {
// here is the problem, if you try to call `this` here
// it will refer to the `myCallback` instead of the parent
// this.resetSaveInterval(5);
// therefore you have to use the cached `self` way
// which coffeescript solved using #variables
self.resetSaveInterval(5);
});
}
Final thought, the # these days means that you are referring to the class instance (i.e., this or self). So, #data basically means this.data, so, without the #, it would refer to any visible variable data on scope.
Test case:
import org.specs2.mutable._
class HelloWorldSpec extends Specification {
"Typesafe Config" should "allow me to see my escaped key" in {
val entries = ConfigFactory.parseString("""
"quoted.key.1" = 5
"quoted.key.2" = 6""").entrySet
entries.head.getKey === "quoted.key.1"
}
}
This test fails because the key is actually "quoted.key.1", not quoted.key.1. Is there a suggested way to unwrap this or do I have to manually look for surrounding quotes and remove them every time?
Read about "Paths, keys, and Config vs. ConfigObject" in the API docs here: http://typesafehub.github.io/config/latest/api/com/typesafe/config/Config.html
and in the README here: https://github.com/typesafehub/config#understanding-config-and-configobject
(Suggestions for improving those docs are welcome.)
The keys in the entry set (and the Config) are path expressions. These are strings that require parsing. There is a parse method in ConfigUtil, see http://typesafehub.github.io/config/latest/api/com/typesafe/config/ConfigUtil.html#splitPath%28java.lang.String%29
It won't work to just remove quotes, the parsing is somewhat more complex than that. Fortunately you can just use the ConfigUtil.splitPath method.
So the two ways to iterate keys at the root level are something like, first with Config:
Config config = ... ;
for (Map.Entry<String, ConfigValue> entry: config.entrySet()) {
String[] keys = ConfigUtil.splitPath(entry.getKey());
System.out.println("Root key = " + keys[0]);
}
Then with ConfigObject:
Config config = ... ;
for (Map.Entry<String, ConfigValue> entry: config.root().entrySet()) {
System.out.println("Root key = " + entry.getKey());
}
I didn't try compiling the above examples so forgive any silly syntax errors.
If your configuration contains only one level (no nested objects) the above two ways of iterating are the same; but if you have nested values they are not the same because iterating Config will give you all leaf values while iterating ConfigObject (config.root()) will give you all immediate children of the root, even if those immediate children are themselves objects.
Say you have:
foo {
bar {
baz = 10
}
}
If you iterate that as a Config you will get one entry which will have the path foo.bar.baz as key and the value 10. If you iterate it as a ConfigObject then you will have one entry which will have the key foo and the value will be an object which will in turn contain the key bar. When iterating this as a Config you could splitPath the foo.bar.baz and you would get an array of three strings, foo, bar, and baz.
To convert a Config to a ConfigObject use the root() method and to convert ConfigObject to Config use the toConfig() method. So config.root().toConfig() == config.
Also, the above config file could be equivalently written as:
foo.bar.baz = 10
But would be different if written as:
"foo.bar.baz" = 10
Because in the first case you have nested objects, and in the second case you have a single object with periods in the key name. This is due to the quotes.
If you write "foo.bar.baz" with quotes, then when iterating Config the path you got back would be quoted and splitPath() would return an array of one element foo.bar.baz. When iterating ConfigObject you would have a single object which would contain an entry with foo.bar.baz as key and 10 as value. Keys containing ., or other special characters, have to be quoted so they are interpreted as single keys rather than as paths.
To make your test case pass you could do this using splitPath:
import org.specs2.mutable._
class HelloWorldSpec extends Specification {
"Typesafe Config" should "allow me to see my escaped key" in {
val entries = ConfigFactory.parseString("""
"quoted.key.1" = 5
"quoted.key.2" = 6""").entrySet
// the ordering of entrySet is not guaranteed so this may
// still fail because it gets quoted.key.2 instead
ConfigUtil.splitPath(entries.head.getKey).head === "quoted.key.1"
}
}
You could also do this, using ConfigObject:
import org.specs2.mutable._
class HelloWorldSpec extends Specification {
"Typesafe Config" should "allow me to see my escaped key" in {
val entries = ConfigFactory.parseString("""
"quoted.key.1" = 5
"quoted.key.2" = 6""").root.entrySet // note ".root." here
// the ordering of entrySet is not guaranteed so this may
// still fail because it gets quoted.key.2 instead
// no need to splitPath because ConfigObject has keys not paths
entries.head.getKey === "quoted.key.1"
}
}
in java, first unwarp the whole ConfigObject to a hashmap, then you can use quoted.key.1 (without surrounding quotes) as key to get correct value.
I am currently using ReSharper V8.1. I've only recently began using ReSharper and have found some interest in their LiveTemplate Macros. I've conjured up a solution to return a list of HotspotItems from a constant, similar to ReSharper's predefined macro "Comma-delimited list of values". In the method I take the constant variable of the template parameter and do a split string on them to provide a collection of HotSpotItems. Unfortunately it doesn't work if I use the macro more than one time within a template. Below is an extreme hack job showing my implementation of the method HotspotItems of IMacroImplementation.
I am hoping that someone out there may have done some work in this area and could possibly provide an example of how they've implemented IMacroImplementation which provides a list of items from a constant and also allows for multiple uses within a single template.
Thank you.
public override HotspotItems GetLookupItems(IHotspotContext context)
{
HotspotItems hotSpotItems = null;
foreach (var hotspot in context.HotspotSession.Hotspots)
{
if (hotspot.Expression != null && ((MacroCallExpressionNew)hotspot.Expression).Definition is Macros.DisplayMultipleItems)
{
//hotspot.CurrentValue
var multiItems = ((MacroCallExpressionNew) hotspot.Expression).Definition as DisplayMultipleItems;
if (!multiItems.ItemSet)
{
var expression = hotspot.Expression as MacroCallExpressionNew;
IMacroParameterValueNew baseValue = expression.Parameters[0].GetValue(context.SessionContext.Solution.GetLifetime(), context.HotspotSession);
string templateValue = baseValue.GetValue();
multiItems.ItemSet = true;
if (!string.IsNullOrEmpty(templateValue) && templateValue.Split(',').Any())
{
var lookupItems = templateValue.Split(',').Select(param => new TextLookupItem(param)).Cast<ILookupItem>().ToList();
if (hotSpotItems == null)
hotSpotItems = new HotspotItems(lookupItems);
else
{
foreach (var item in lookupItems)
{
hotSpotItems.Items.Add(item);
}
}
}
}
}
}
return hotSpotItems;
}
You should fire up dotPeek and point it to the ReSharper bin directory and take a look at ListMacroDef and ListMacroImpl, which is the implementation for the comma-delimited list macro.
The definition derives from SimpleMacroDefinition. It gets given the parameters in the call to GetPlaceholder, looks at the first and splits it by comma, returning the first item as the placeholder.
ListMacroImpl is just as simple. Its constructor has an [Optional] parameter of type MacroParameterValueCollection. This is the list of parameter values specified in the hotspot editor. You'll want to check for null and take the first parameter, which will be your delimited list. It then overrides GetLookupItems and returns HotspotItems.Empty if the parameter value is null, or parses the value and returns a list of TextLookupItem.
You don't need to look at the session and list of hotspots - that will get you all hotspots in the session, when you're only interested in the current hotspot, and ReSharper will create a new IMacroImplementation for each hotspot and give you those values in your constructor.