11package cmd
22
33import (
4+ "bytes"
45 "flag"
6+ "fmt"
57 "os"
8+ "os/exec"
69 "strings"
710
811 "github.com/mitchellh/cli"
@@ -19,12 +22,14 @@ func NewProvisionCommand(ui cli.Ui, trellis *trellis.Trellis) *ProvisionCommand
1922}
2023
2124type 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
2833}
2934
3035func (c * ProvisionCommand ) init () {
@@ -33,6 +38,8 @@ func (c *ProvisionCommand) init() {
3338 c .flags .StringVar (& c .extraVars , "extra-vars" , "" , "Additional variables which are passed through to Ansible as 'extra-vars'" )
3439 c .flags .StringVar (& c .tags , "tags" , "" , "only run roles and tasks tagged with these values" )
3540 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" )
3643}
3744
3845func (c * ProvisionCommand ) Run (args []string ) int {
@@ -65,11 +72,15 @@ func (c *ProvisionCommand) Run(args []string) int {
6572 return 1
6673 }
6774
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+ }
7081
7182 playbook := ansible.Playbook {
72- Name : "server.yml" ,
83+ Name : c . playbookName ,
7384 Env : environment ,
7485 Verbose : c .verbose ,
7586 }
@@ -79,15 +90,41 @@ func (c *ProvisionCommand) Run(args []string) int {
7990 }
8091
8192 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+
8297 playbook .AddArg ("--tags" , c .tags )
8398 }
8499
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+
85121 if environment == "development" {
86- os .Setenv ("ANSIBLE_HOST_KEY_CHECKING" , "false" )
87- playbook .SetName ("dev.yml" )
88122 playbook .SetInventory (findDevInventory (c .Trellis , c .UI ))
89123 }
90124
125+ galaxyInstallCommand := & GalaxyInstallCommand {c .UI , c .Trellis }
126+ galaxyInstallCommand .Run ([]string {})
127+
91128 provision := command .WithOptions (
92129 command .WithUiOutput (c .UI ),
93130 command .WithLogging (c .UI ),
@@ -130,11 +167,16 @@ Provision and provide extra vars to Ansible:
130167
131168 $ trellis provision --extra-vars key=value production
132169
170+ Provision using interactive mode to select tags:
171+
172+ $ trellis provision -i production
173+
133174Arguments:
134175 ENVIRONMENT Name of environment (ie: production)
135176
136177Options:
137178 --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)
138180 --tags (multiple) Only run roles and tasks tagged with these values
139181 --verbose Enable Ansible's verbose mode
140182 -h, --help Show this help
@@ -149,8 +191,64 @@ func (c *ProvisionCommand) AutocompleteArgs() complete.Predictor {
149191
150192func (c * ProvisionCommand ) AutocompleteFlags () complete.Flags {
151193 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 ,
155199 }
156200}
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