11package cmd
22
33import (
4+ "bytes"
45 "flag"
6+ "fmt"
57 "os"
8+ "os/exec"
69 "strings"
710
811 "github.com/hashicorp/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,43 @@ func (c *ProvisionCommand) Run(args []string) int {
7990 }
8091
8192 if c .tags != "" {
93+ if c .interactive {
94+ c .UI .Error ("Error: --interactive and --tags cannot be used together. Please use one or the other." )
95+ return 1
96+ }
97+
8298 playbook .AddArg ("--tags" , c .tags )
8399 }
84100
101+ if c .interactive {
102+ _ , err := exec .LookPath ("fzf" )
103+ if err != nil {
104+ c .UI .Error ("Error: `fzf` command found. fzf is required to use interactive mode." )
105+ return 1
106+ }
107+
108+ tags , err := c .getTags ()
109+ if err != nil {
110+ c .UI .Error (err .Error ())
111+ return 1
112+ }
113+
114+ selectedTags , err := c .selectedTagsFromFzf (tags )
115+ if err != nil {
116+ c .UI .Error (err .Error ())
117+ return 1
118+ }
119+
120+ playbook .AddArg ("--tags" , strings .Join (selectedTags , "," ))
121+ }
122+
85123 if environment == "development" {
86- os .Setenv ("ANSIBLE_HOST_KEY_CHECKING" , "false" )
87- playbook .SetName ("dev.yml" )
88124 playbook .SetInventory (findDevInventory (c .Trellis , c .UI ))
89125 }
90126
127+ galaxyInstallCommand := & GalaxyInstallCommand {c .UI , c .Trellis }
128+ galaxyInstallCommand .Run ([]string {})
129+
91130 provision := command .WithOptions (
92131 command .WithUiOutput (c .UI ),
93132 command .WithLogging (c .UI ),
@@ -130,11 +169,16 @@ Provision and provide extra vars to Ansible:
130169
131170 $ trellis provision --extra-vars key=value production
132171
172+ Provision using interactive mode to select tags:
173+
174+ $ trellis provision -i production
175+
133176Arguments:
134177 ENVIRONMENT Name of environment (ie: production)
135178
136179Options:
137180 --extra-vars (multiple) Set additional variables as key=value or YAML/JSON, if filename prepend with @
181+ -i, --interactive Enter interactive mode to select tags to provision (requires fzf)
138182 --tags (multiple) Only run roles and tasks tagged with these values
139183 --verbose Enable Ansible's verbose mode
140184 -h, --help Show this help
@@ -149,8 +193,65 @@ func (c *ProvisionCommand) AutocompleteArgs() complete.Predictor {
149193
150194func (c * ProvisionCommand ) AutocompleteFlags () complete.Flags {
151195 return complete.Flags {
152- "--extra-vars" : complete .PredictNothing ,
153- "--tags" : complete .PredictNothing ,
154- "--verbose" : complete .PredictNothing ,
196+ "-i" : complete .PredictNothing ,
197+ "--interactive" : complete .PredictNothing ,
198+ "--extra-vars" : complete .PredictNothing ,
199+ "--tags" : complete .PredictNothing ,
200+ "--verbose" : complete .PredictNothing ,
155201 }
156202}
203+
204+ func (c * ProvisionCommand ) getTags () ([]string , error ) {
205+ tagsPlaybook := ansible.Playbook {
206+ Name : c .playbookName ,
207+ Env : c .flags .Arg (0 ),
208+ Args : []string {"--list-tags" },
209+ }
210+
211+ tagsProvision := command .WithOptions (
212+ command .WithUiOutput (c .UI ),
213+ ).Cmd ("ansible-playbook" , tagsPlaybook .CmdArgs ())
214+
215+ output := & bytes.Buffer {}
216+ tagsProvision .Stdout = output
217+
218+ if err := tagsProvision .Run (); err != nil {
219+ return nil , err
220+ }
221+
222+ tags := ansible .ParseTags (output .String ())
223+
224+ return tags , nil
225+ }
226+
227+ func (c * ProvisionCommand ) selectedTagsFromFzf (tags []string ) ([]string , error ) {
228+ output := & bytes.Buffer {}
229+ input := strings .NewReader (strings .Join (tags , "\n " ))
230+
231+ previewCmd := fmt .Sprintf ("trellis exec ansible-playbook %s --list-tasks --tags {}" , c .playbookName )
232+
233+ fzf := command .WithOptions (command .WithTermOutput ()).Cmd (
234+ "fzf" ,
235+ []string {
236+ "-m" ,
237+ "--height" , "50%" ,
238+ "--reverse" ,
239+ "--border" ,
240+ "--border-label" , "Select tags to provision (use TAB to select multiple tags)" ,
241+ "--border-label-pos" , "5" ,
242+ "--preview" , previewCmd ,
243+ "--preview-label" , "Tasks for tag" ,
244+ },
245+ )
246+ fzf .Stdin = input
247+ fzf .Stdout = output
248+
249+ err := fzf .Run ()
250+ if err != nil {
251+ return nil , err
252+ }
253+
254+ selectedTags := strings .Split (strings .TrimSpace (output .String ()), "\n " )
255+
256+ return selectedTags , nil
257+ }
0 commit comments