Skip to content

Commit faf70f2

Browse files
committed
Add "triggers" feature
Signed-off-by: Andy Doan <andy@foundries.io>
1 parent 22ad702 commit faf70f2

3 files changed

Lines changed: 142 additions & 0 deletions

File tree

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
github.com/foundriesio/go-ecies v0.3.0
1212
github.com/hashicorp/go-version v1.7.0
1313
github.com/karrick/godiff v0.0.2
14+
github.com/matoous/go-nanoid/v2 v2.1.0
1415
github.com/mitchellh/go-homedir v1.1.0
1516
github.com/pelletier/go-toml v1.9.5
1617
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTRe
137137
github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
138138
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
139139
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
140+
github.com/matoous/go-nanoid/v2 v2.1.0 h1:P64+dmq21hhWdtvZfEAofnvJULaRR1Yib0+PnU669bE=
141+
github.com/matoous/go-nanoid/v2 v2.1.0/go.mod h1:KlbGNQ+FhrUNIHUxZdL63t7tl4LaPkZNpUULS8H4uVM=
140142
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
141143
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
142144
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=

subcommands/devices/trigger.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package devices
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"os"
7+
"strings"
8+
9+
"github.com/foundriesio/fioctl/client"
10+
"github.com/foundriesio/fioctl/subcommands"
11+
nanoid "github.com/matoous/go-nanoid/v2"
12+
"github.com/spf13/cobra"
13+
"golang.org/x/exp/slices"
14+
)
15+
16+
func init() {
17+
triggerCmd := &cobra.Command{
18+
Use: "trigger",
19+
Short: "Trigger remote actions on device",
20+
Long: `
21+
*NOTE*: Requires devices running LmP version 97 or later.
22+
23+
This command sets a fioconfig entry that will let a device know it needs to
24+
run a given trigger *once* and report its results back to the device gateway's
25+
"tests" API. You can take the "command ID" from this command's output to then
26+
look up the results using the "fioctl devices tests" command.
27+
28+
# See what triggers are available to the device:
29+
$ fioctl devices trigger <device>
30+
31+
# Initiate a remote trigger:
32+
$ fioctl devices trigger <device> <trigger>
33+
`,
34+
Args: cobra.RangeArgs(1, 2),
35+
Run: doTrigger,
36+
}
37+
triggerCmd.Flags().StringP("reason", "r", "", "The reason for running this command")
38+
cmd.AddCommand(triggerCmd)
39+
}
40+
41+
func loadRemoteActions(d client.DeviceApi) []string {
42+
dcl, err := d.ListConfig()
43+
subcommands.DieNotNil(err)
44+
if len(dcl.Configs) > 0 {
45+
for _, cfgFile := range dcl.Configs[0].Files {
46+
if cfgFile.Name == "fio-remote-actions" {
47+
var actions []string
48+
for _, action := range strings.Split(cfgFile.Value, ",") {
49+
action = strings.TrimSpace(action)
50+
if len(action) > 0 {
51+
actions = append(actions, action)
52+
}
53+
}
54+
if actions == nil {
55+
break
56+
}
57+
return actions
58+
}
59+
}
60+
}
61+
fmt.Println("Remote actions are not configured for this device")
62+
os.Exit(0)
63+
return nil
64+
}
65+
66+
func doTrigger(cmd *cobra.Command, args []string) {
67+
name := args[0]
68+
69+
// Quick sanity check for device
70+
d := getDevice(cmd, name)
71+
72+
// See what triggers are allowed
73+
allowed := loadRemoteActions(d.Api)
74+
75+
if len(args) == 1 {
76+
fmt.Println("Available actions:")
77+
for _, trigger := range allowed {
78+
fmt.Println("*", trigger)
79+
}
80+
return
81+
}
82+
83+
action := args[1]
84+
if !slices.Contains(allowed, action) {
85+
subcommands.DieNotNil(fmt.Errorf("Invalid action: %s", action))
86+
}
87+
reason, _ := cmd.Flags().GetString("reason")
88+
89+
// For the random part anything >= 15 characters will give us about 1,000
90+
// years before a collision (assuming 100 IDs per second):
91+
// https://zelark.github.io/nano-id-cc/
92+
idLen := 15
93+
maxActionLen := 48 - idLen - 1 // 1 for the underscore in the CommandId below
94+
if len(action) > maxActionLen { // Device Gateway has max len of 48
95+
subcommands.DieNotNil(fmt.Errorf("Action name(%s) too long. Max length is %d", action, maxActionLen))
96+
}
97+
id, err := nanoid.New(idLen)
98+
subcommands.DieNotNil(err)
99+
100+
opts := triggerOptions{
101+
Capture: true,
102+
Command: action,
103+
CommandId: fmt.Sprintf("%s_%s", action, id),
104+
Reason: reason,
105+
}
106+
107+
ccr := opts.AsConfig()
108+
subcommands.DieNotNil(d.Api.PatchConfig(ccr, false))
109+
fmt.Println("Config change submitted. Command ID is:", opts.CommandId)
110+
}
111+
112+
type triggerOptions struct {
113+
CommandId string
114+
Command string
115+
Capture bool
116+
Reason string
117+
}
118+
119+
func (o triggerOptions) AsConfig() client.ConfigCreateRequest {
120+
b := new(bytes.Buffer)
121+
capture := 0
122+
if o.Capture {
123+
capture = 1
124+
}
125+
fmt.Fprintf(b, "COMMAND_ID=%s\n", o.CommandId)
126+
fmt.Fprintf(b, "COMMAND_CAPTURE=%d\n", capture)
127+
128+
return client.ConfigCreateRequest{
129+
Reason: o.Reason,
130+
Files: []client.ConfigFile{
131+
{
132+
Name: "fioconfig-oneshot-" + o.Command,
133+
Value: b.String(),
134+
Unencrypted: true,
135+
OnChanged: []string{"/usr/share/fioconfig/handlers/fioconfig-oneshot", o.Command},
136+
},
137+
},
138+
}
139+
}

0 commit comments

Comments
 (0)