Scripting with ciao
The cli has two commands that present information to the user: show and list
$ ciao list instances
By default, these commands format their output in a style that is pleasing to the human eye. For example,
$ ciao show instance 45e56df2-350b-49f0-b394-f949cdf6b3b6
PrivateAddresses: [{172.16.0.2 02:00:ac:10:00:02}]
Created: 2018-01-04 16:15:51.767182887 +0000 UTC
WorkloadID: 22368826-b7ba-4f97-8400-57276b1c383c
NodeID: 4879795e-18f0-4c18-9be3-eb21e5c6df12
ID: 45e56df2-350b-49f0-b394-f949cdf6b3b6
Name:
Volumes: [af75d901-2dcd-41fc-9827-3b30d320577c]
Status: active
TenantID: b5df368f-c097-437c-90d2-620a5d1bb0b0
SSHIP: 198.51.100.71
SSHPort: 33002
However, this is not always what we want, particularly if we are writing a script to automate a set of ciao commands. For example, say we wanted to programmatically retrieve the ssh connection details for the above instance. Using the command above we’d need to do some scripting to ignore the first 7 lines and extract the IP and port number from lines 8 and 9. Nasty.
Luckily the ciao show and list commands accept a -f option which is specified along with a Go template. These templates are little programs that can be used to extract the specific data we are interested in. For example, to extract the SSH IP and port numbers we would issue the following command.
$ ciao show instance 45e56df2-350b-49f0-b394-f949cdf6b3b6 -f '{{.SSHIP}}:{{.SSHPort}}'
198.51.100.71:33002
No parsing required.
Check the help for each individual show and list subcommand to discover which fields, e.g., SSHIP, are supported. For example,
$ ciao help show instance
Show information about an instance
Usage:
ciao show instance ID [flags]
Flags:
-h, --help help for instance
Global Flags:
-f, --template string Template used to format output
When using the template flag the following structure is provided:
struct {
PrivateAddresses []struct {
Addr string `json:"addr"`
MacAddr string `json:"mac_addr"`
} `json:"private_addresses"`
Created time.Time `json:"created"`
WorkloadID string `json:"workload_id"`
NodeID string `json:"node_id"`
ID string `json:"id"`
Name string `json:"name"`
Volumes []string `json:"volumes"`
Status string `json:"status"`
TenantID string `json:"tenant_id"`
SSHIP string `json:"ssh_ip"`
SSHPort int `json:"ssh_port"`
}
Template Cheat Sheet
Let’s look at a few more examples of how we can use templates to extract information from the ciao command. Looking at the help of the ciao show instance command shown above we can see that ciao passes a structure to the template passed to the -f option. The members of this structure can be accessed inside template code by prefixing their name with ‘.’. For example the following command prints out the ID of an instance.
$ ciao show instance 45e56df2-350b-49f0-b394-f949cdf6b3b6 -f '{{.ID}}'
45e56df2-350b-49f0-b394-f949cdf6b3b6
Note the command prints out the instance without a newline which can be a bit confusing. We can fix this by including a newline character directly in the template.
$ ciao show instance 45e56df2-350b-49f0-b394-f949cdf6b3b6 -f '{{.ID}}
'
45e56df2-350b-49f0-b394-f949cdf6b3b6
or by using the println function
$ ciao show instance 45e56df2-350b-49f0-b394-f949cdf6b3b6 -f '{{println .ID}}'
45e56df2-350b-49f0-b394-f949cdf6b3b6
Here’s a more elaborate example in which we output the id, the status and ssh connection details of the instance.
$ ciao show instance 45e56df2-350b-49f0-b394-f949cdf6b3b6 -f '{{.ID}} ({{.Status}}) {{.SSHIP}}:{{.SSHPort}}{{println}}'
45e56df2-350b-49f0-b394-f949cdf6b3b6 (active) 198.51.100.71:33002
Now let’s take a look at the PrivateAddresses field. This field is a slice of structures. We can gain access to this slice as follows .PrivateAddresses Let’s output the slice to see what happens.
$ ciao show instance 45e56df2-350b-49f0-b394-f949cdf6b3b6 -f '{{println .PrivateAddresses}}'
[{172.16.0.2 02:00:ac:10:00:02}]
We see what appears to be a slice of structures. We can use the template range and index commands to access the elements of this slice. For example to output the MAC addresses of each structure we would type:
$ ciao show instance 45e56df2-350b-49f0-b394-f949cdf6b3b6 -f '{{range .PrivateAddresses}}{{println .MacAddr}}{{end}}'
02:00:ac:10:00:02
Note that inside the tags the meaning of the . cursor changes. Rather than referring to the entire structure passed to template it refers to an individual element of the .PrivateAddresses slice. If you want to access a field of the top level structure inside the range statement you need to use the $ operator. For example, the following command prints the NodeID of the instance before it prints each MAC address.
$ ciao show instance 45e56df2-350b-49f0-b394-f949cdf6b3b6 -f '{{range .PrivateAddresses}}{{$.NodeID}} : {{println .MacAddr}}{{end}}'
4879795e-18f0-4c18-9be3-eb21e5c6df12 : 02:00:ac:10:00:02
If we are only interested in the MAC address of a specific element of the slice we can access it directly.
$ ciao show instance 45e56df2-350b-49f0-b394-f949cdf6b3b6 -f '{{println (index .PrivateAddresses 0).MacAddr}}'
02:00:ac:10:00:02
If you find the expression (index .PrivateAddresses 0).MacAddr a little confusing you can split things up by introducing a new variable.
$ ciao show instance 45e56df2-350b-49f0-b394-f949cdf6b3b6 -f '{{$addr := index .PrivateAddresses 0}}{{println $addr.MacAddr}}'
02:00:ac:10:00:02
Here the $addr variable becomes the first element of the slice. $addr itself is a structure and so we can use the . operator to access its fields.
However, there’s a problem with this approach. We don’t know in advance how many entries are present in the .PrivateAddresses slice. If we try to index an element that doesn’t exist we’ll get an error. For example,
$ ciao show instance 45e56df2-350b-49f0-b394-f949cdf6b3b6 -f '{{$addr := index .PrivateAddresses 1}}{{println $addr.MacAddr}}'
Error: Error generating template output: template: :1:11: executing "" at <index .PrivateAddres...>: error calling index: index out of range: 1
We can use the if statement to prevent us from accessing non-existing elements, e.g.,
$ ciao show instance 45e56df2-350b-49f0-b394-f949cdf6b3b6 -f '{{if gt (len .PrivateAddresses) 1}}{{$addr := index .PrivateAddresses 1}}{{println $addr.MacAddr}}{{end}}'
The command prints nothing as our if statement evaluates to false. There’s only one element in .PrivateAddresses slice.
Note in the previous example we use the rather unwieldy .PrivateAddresses twice. We can eliminate the repetition using the with statement, e.g.,
$ ciao show instance 45e56df2-350b-49f0-b394-f949cdf6b3b6 -f '{{with .PrivateAddresses}}{{if gt (len .) 1}}{{$addr := index . 1}}{{println $addr.MacAddr}}{{end}}{{end}}'
Inside the with statement the . cursor takes on a new meaning. It becomes assigned to the value of .PrivateAddresses.
The example above is getting a little big to fit onto one line. We might find it easier to read if it were split onto multiple lines, e.g.,
$ ciao show instance 45e56df2-350b-49f0-b394-f949cdf6b3b6 -f '
{{with .PrivateAddresses}}
{{if gt (len .) 1}}
{{$addr := index . 1}}
{{println $addr.MacAddr}}
{{end}}
{{end}}'
This is easier to read but unfortunately the newlines in the template that we added to improve readability get copied to the output. We can fix this by appending a ‘-‘ after the {{s and before the }}s, e.g.,
$ ciao show instance 45e56df2-350b-49f0-b394-f949cdf6b3b6 -f '
{{- with .PrivateAddresses}}
{{- if gt (len .) 1}}
{{- $addr := index . 1}}
{{- println $addr.MacAddr}}
{{- end}}
{{- end -}}'
The ‘-‘s gobble up the white space that occurs before the {{- and after the -}}.
Here’s one final example. Let’s take a look at the help for the ciao list workloads command.
$ ciao help list workloads
List workloads.
Usage:
ciao list workloads [flags]
Flags:
-h, --help help for workloads
Global Flags:
-f, --template string Template used to format output
When using the template flag the following structure is provided:
[]struct {
ID string `json:"id"`
Name string `json:"name"`
CPUs int `json:"vcpus"`
Mem int `json:"ram"`
}
The important thing to note here is that the template is passed a slice of structures. That means we need to use template function that can handle slices, e.g., len, index or range. So to determine the number of workloads available we would type.
$ ciao list workloads -f '{{println (len .)}}'
4
To output the names of each workload we might do
$ ciao list workloads -f '{{range .}}{{println .Name}}{{end}}'
Clear Linux test VM
Debian latest test container
Ubuntu latest test container
Ubuntu test VM