1
1
package cmd
2
2
3
3
import (
4
+ "bytes"
4
5
"flag"
6
+ "fmt"
5
7
"os"
8
+ "os/exec"
6
9
"strings"
7
10
8
11
"github.com/mitchellh/cli"
@@ -19,12 +22,14 @@ func NewProvisionCommand(ui cli.Ui, trellis *trellis.Trellis) *ProvisionCommand
19
22
}
20
23
21
24
type ProvisionCommand struct {
22
- UI cli.Ui
23
- flags * flag.FlagSet
24
- extraVars string
25
- tags string
26
- Trellis * trellis.Trellis
27
- verbose bool
25
+ UI cli.Ui
26
+ flags * flag.FlagSet
27
+ extraVars string
28
+ interactive bool
29
+ playbookName string
30
+ tags string
31
+ Trellis * trellis.Trellis
32
+ verbose bool
28
33
}
29
34
30
35
func (c * ProvisionCommand ) init () {
@@ -33,6 +38,8 @@ func (c *ProvisionCommand) init() {
33
38
c .flags .StringVar (& c .extraVars , "extra-vars" , "" , "Additional variables which are passed through to Ansible as 'extra-vars'" )
34
39
c .flags .StringVar (& c .tags , "tags" , "" , "only run roles and tasks tagged with these values" )
35
40
c .flags .BoolVar (& c .verbose , "verbose" , false , "Enable Ansible's verbose mode" )
41
+ c .flags .BoolVar (& c .interactive , "interactive" , false , "Enable interactive mode to select tags to provision" )
42
+ c .flags .BoolVar (& c .interactive , "i" , false , "Enable interactive mode to select tags to provision" )
36
43
}
37
44
38
45
func (c * ProvisionCommand ) Run (args []string ) int {
@@ -65,11 +72,15 @@ func (c *ProvisionCommand) Run(args []string) int {
65
72
return 1
66
73
}
67
74
68
- galaxyInstallCommand := & GalaxyInstallCommand {c .UI , c .Trellis }
69
- galaxyInstallCommand .Run ([]string {})
75
+ c .playbookName = "server.yml"
76
+
77
+ if environment == "development" {
78
+ c .playbookName = "dev.yml"
79
+ os .Setenv ("ANSIBLE_HOST_KEY_CHECKING" , "false" )
80
+ }
70
81
71
82
playbook := ansible.Playbook {
72
- Name : "server.yml" ,
83
+ Name : c . playbookName ,
73
84
Env : environment ,
74
85
Verbose : c .verbose ,
75
86
}
@@ -79,15 +90,41 @@ func (c *ProvisionCommand) Run(args []string) int {
79
90
}
80
91
81
92
if c .tags != "" {
93
+ if c .interactive {
94
+ c .UI .Error ("--interactive and --tags cannot be used together. Please use one or the other." )
95
+ }
96
+
82
97
playbook .AddArg ("--tags" , c .tags )
83
98
}
84
99
100
+ if c .interactive {
101
+ _ , err := exec .LookPath ("fzf" )
102
+ if err != nil {
103
+ c .UI .Error ("No `fzf` command found. fzf is required to use interactive mode." )
104
+ }
105
+
106
+ tags , err := c .getTags ()
107
+ if err != nil {
108
+ c .UI .Error (err .Error ())
109
+ return 1
110
+ }
111
+
112
+ selectedTags , err := c .selectedTagsFromFzf (tags )
113
+ if err != nil {
114
+ c .UI .Error (err .Error ())
115
+ return 1
116
+ }
117
+
118
+ playbook .AddArg ("--tags" , strings .Join (selectedTags , "," ))
119
+ }
120
+
85
121
if environment == "development" {
86
- os .Setenv ("ANSIBLE_HOST_KEY_CHECKING" , "false" )
87
- playbook .SetName ("dev.yml" )
88
122
playbook .SetInventory (findDevInventory (c .Trellis , c .UI ))
89
123
}
90
124
125
+ galaxyInstallCommand := & GalaxyInstallCommand {c .UI , c .Trellis }
126
+ galaxyInstallCommand .Run ([]string {})
127
+
91
128
provision := command .WithOptions (
92
129
command .WithUiOutput (c .UI ),
93
130
command .WithLogging (c .UI ),
@@ -130,11 +167,16 @@ Provision and provide extra vars to Ansible:
130
167
131
168
$ trellis provision --extra-vars key=value production
132
169
170
+ Provision using interactive mode to select tags:
171
+
172
+ $ trellis provision -i production
173
+
133
174
Arguments:
134
175
ENVIRONMENT Name of environment (ie: production)
135
176
136
177
Options:
137
178
--extra-vars (multiple) Set additional variables as key=value or YAML/JSON, if filename prepend with @
179
+ -i, --interactive Enter interactive mode to select tags to provision (requires fzf)
138
180
--tags (multiple) Only run roles and tasks tagged with these values
139
181
--verbose Enable Ansible's verbose mode
140
182
-h, --help Show this help
@@ -149,8 +191,64 @@ func (c *ProvisionCommand) AutocompleteArgs() complete.Predictor {
149
191
150
192
func (c * ProvisionCommand ) AutocompleteFlags () complete.Flags {
151
193
return complete.Flags {
152
- "--extra-vars" : complete .PredictNothing ,
153
- "--tags" : complete .PredictNothing ,
154
- "--verbose" : complete .PredictNothing ,
194
+ "-i" : complete .PredictNothing ,
195
+ "--interactive" : complete .PredictNothing ,
196
+ "--extra-vars" : complete .PredictNothing ,
197
+ "--tags" : complete .PredictNothing ,
198
+ "--verbose" : complete .PredictNothing ,
155
199
}
156
200
}
201
+
202
+ func (c * ProvisionCommand ) getTags () ([]string , error ) {
203
+ tagsPlaybook := ansible.Playbook {
204
+ Name : c .playbookName ,
205
+ Env : c .flags .Arg (0 ),
206
+ Args : []string {"--list-tags" },
207
+ }
208
+
209
+ tagsProvision := command .WithOptions (
210
+ command .WithUiOutput (c .UI ),
211
+ ).Cmd ("ansible-playbook" , tagsPlaybook .CmdArgs ())
212
+
213
+ output := & bytes.Buffer {}
214
+ tagsProvision .Stdout = output
215
+
216
+ if err := tagsProvision .Run (); err != nil {
217
+ return nil , err
218
+ }
219
+
220
+ tags := ansible .ParseTags (output .String ())
221
+
222
+ return tags , nil
223
+ }
224
+
225
+ func (c * ProvisionCommand ) selectedTagsFromFzf (tags []string ) ([]string , error ) {
226
+ output := & bytes.Buffer {}
227
+ input := strings .NewReader (strings .Join (tags , "\n " ))
228
+
229
+ previewCmd := fmt .Sprintf ("trellis exec ansible-playbook %s --list-tasks --tags {}" , c .playbookName )
230
+
231
+ fzf := command .WithOptions (command .WithTermOutput ()).Cmd (
232
+ "fzf" ,
233
+ []string {
234
+ "-m" ,
235
+ "--height" , "50%" ,
236
+ "--reverse" ,
237
+ "--border" ,
238
+ "--header" , "Select tags to provision (use TAB to select multiple tags)" ,
239
+ "--header-first" ,
240
+ "--preview" , previewCmd ,
241
+ },
242
+ )
243
+ fzf .Stdin = input
244
+ fzf .Stdout = output
245
+
246
+ err := fzf .Run ()
247
+ if err != nil {
248
+ return nil , err
249
+ }
250
+
251
+ selectedTags := strings .Split (strings .TrimSpace (output .String ()), "\n " )
252
+
253
+ return selectedTags , nil
254
+ }
0 commit comments