github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/romulus/agree/agree.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package agree 5 6 import ( 7 "bufio" 8 "bytes" 9 "fmt" 10 "os" 11 "os/exec" 12 "strings" 13 14 "github.com/juju/cmd" 15 "github.com/juju/errors" 16 "github.com/juju/gnuflag" 17 "github.com/juju/juju/cmd/modelcmd" 18 "github.com/juju/juju/cmd/output" 19 "github.com/juju/terms-client/api" 20 "github.com/juju/terms-client/api/wireformat" 21 "gopkg.in/juju/charm.v6-unstable" 22 ) 23 24 var ( 25 clientNew = api.NewClient 26 ) 27 28 const agreeDoc = ` 29 Agree to the terms required by a charm. 30 31 When deploying a charm that requires agreement to terms, use 'juju agree' to 32 view the terms and agree to them. Then the charm may be deployed. 33 34 Once you have agreed to terms, you will not be prompted to view them again. 35 36 Examples: 37 # Displays terms for somePlan revision 1 and prompts for agreement. 38 juju agree somePlan/1 39 40 # Displays the terms for revision 1 of somePlan, revision 2 of otherPlan, 41 # and prompts for agreement. 42 juju agree somePlan/1 otherPlan/2 43 44 # Agrees to the terms without prompting. 45 juju agree somePlan/1 otherPlan/2 --yes 46 ` 47 48 // NewAgreeCommand returns a new command that can be 49 // used to create user agreements. 50 func NewAgreeCommand() cmd.Command { 51 return &agreeCommand{} 52 } 53 54 type term struct { 55 owner string 56 name string 57 revision int 58 } 59 60 // agreeCommand creates a user agreement to the specified terms. 61 type agreeCommand struct { 62 modelcmd.JujuCommandBase 63 out cmd.Output 64 65 terms []term 66 termIds []string 67 SkipTermContent bool 68 } 69 70 // SetFlags implements Command.SetFlags. 71 func (c *agreeCommand) SetFlags(f *gnuflag.FlagSet) { 72 c.JujuCommandBase.SetFlags(f) 73 f.BoolVar(&c.SkipTermContent, "yes", false, "Agree to terms non interactively") 74 c.out.AddFlags(f, "json", output.DefaultFormatters) 75 } 76 77 // Info implements Command.Info. 78 func (c *agreeCommand) Info() *cmd.Info { 79 return &cmd.Info{ 80 Name: "agree", 81 Args: "<term>", 82 Purpose: "Agree to terms.", 83 Doc: agreeDoc, 84 } 85 } 86 87 // Init read and verifies the arguments. 88 func (c *agreeCommand) Init(args []string) error { 89 if len(args) < 1 { 90 return errors.New("missing arguments") 91 } 92 93 for _, t := range args { 94 termId, err := charm.ParseTerm(t) 95 if err != nil { 96 return errors.Annotate(err, "invalid term format") 97 } 98 if termId.Revision == 0 { 99 return errors.Errorf("must specify a valid term revision %q", t) 100 } 101 c.terms = append(c.terms, term{owner: termId.Owner, name: termId.Name, revision: termId.Revision}) 102 c.termIds = append(c.termIds, t) 103 } 104 if len(c.terms) == 0 { 105 return errors.New("must specify a valid term revision") 106 } 107 return nil 108 } 109 110 // Run implements Command.Run. 111 func (c *agreeCommand) Run(ctx *cmd.Context) error { 112 client, err := c.BakeryClient() 113 if err != nil { 114 return errors.Trace(err) 115 } 116 117 termsClient, err := clientNew(api.HTTPClient(client)) 118 if err != nil { 119 return err 120 } 121 122 if c.SkipTermContent { 123 err := saveAgreements(ctx, termsClient, c.terms) 124 if err != nil { 125 return errors.Trace(err) 126 } 127 return nil 128 } 129 130 needAgreement := []wireformat.GetTermsResponse{} 131 terms, err := termsClient.GetUnsignedTerms(&wireformat.CheckAgreementsRequest{ 132 Terms: c.termIds, 133 }) 134 if err != nil { 135 return errors.Annotate(err, "failed to retrieve terms") 136 } 137 needAgreement = append(needAgreement, terms...) 138 139 if len(needAgreement) == 0 { 140 fmt.Fprintf(ctx.Stdout, "Already agreed\n") 141 return nil 142 } 143 144 err = printTerms(ctx, needAgreement) 145 if err != nil { 146 return errors.Trace(err) 147 } 148 fmt.Fprintf(ctx.Stdout, "Do you agree to the displayed terms? (Y/n): ") 149 answer, err := userAnswer() 150 if err != nil { 151 return errors.Trace(err) 152 } 153 154 agreedTerms := make([]term, len(needAgreement)) 155 for i, t := range needAgreement { 156 agreedTerms[i] = term{owner: t.Owner, name: t.Name, revision: t.Revision} 157 } 158 159 answer = strings.TrimSpace(answer) 160 if userAgrees(answer) { 161 err = saveAgreements(ctx, termsClient, agreedTerms) 162 if err != nil { 163 return errors.Trace(err) 164 } 165 } else { 166 fmt.Fprintf(ctx.Stdout, "You didn't agree to the presented terms.\n") 167 return nil 168 } 169 170 return nil 171 } 172 173 func saveAgreements(ctx *cmd.Context, termsClient api.Client, ts []term) error { 174 agreements := make([]wireformat.SaveAgreement, len(ts)) 175 for i, t := range ts { 176 agreements[i] = wireformat.SaveAgreement{ 177 TermOwner: t.owner, 178 TermName: t.name, 179 TermRevision: t.revision, 180 } 181 } 182 response, err := termsClient.SaveAgreement(&wireformat.SaveAgreements{Agreements: agreements}) 183 if err != nil { 184 return errors.Annotate(err, "failed to save user agreement") 185 } 186 for _, agreement := range response.Agreements { 187 termName := agreement.Term 188 if agreement.Owner != "" { 189 termName = fmt.Sprintf("%v/%v", agreement.Owner, agreement.Term) 190 } 191 _, err = fmt.Fprintf(ctx.Stdout, "Agreed to revision %v of %v for Juju users\n", agreement.Revision, termName) 192 if err != nil { 193 return errors.Trace(err) 194 } 195 } 196 return nil 197 } 198 199 var userAnswer = func() (string, error) { 200 return bufio.NewReader(os.Stdin).ReadString('\n') 201 } 202 203 func printTerms(ctx *cmd.Context, terms []wireformat.GetTermsResponse) (returnErr error) { 204 output := "" 205 for _, t := range terms { 206 if t.Owner != "" { 207 output += fmt.Sprintf(` 208 === %v/%v/%v: %v === 209 %v 210 ======== 211 `, t.Owner, t.Name, t.Revision, t.CreatedOn, t.Content) 212 } else { 213 output += fmt.Sprintf(` 214 === %v/%v: %v === 215 %v 216 ======== 217 `, t.Name, t.Revision, t.CreatedOn, t.Content) 218 } 219 } 220 defer func() { 221 if returnErr != nil { 222 _, err := fmt.Fprint(ctx.Stdout, output) 223 returnErr = errors.Annotate(err, "failed to print plan") 224 } 225 }() 226 227 buffer := bytes.NewReader([]byte(output)) 228 pager, err := pagerCmd() 229 if err != nil { 230 return err 231 } 232 pager.Stdout = ctx.Stdout 233 pager.Stdin = buffer 234 err = pager.Run() 235 return errors.Annotate(err, "failed to print plan") 236 } 237 238 func pagerCmd() (*exec.Cmd, error) { 239 os.Unsetenv("LESS") 240 if pager := os.Getenv("PAGER"); pager != "" { 241 if pagerPath, err := exec.LookPath(pager); err == nil { 242 return exec.Command(pagerPath), nil 243 } 244 } 245 if lessPath, err := exec.LookPath("less"); err == nil { 246 return exec.Command(lessPath, "-P", "Press 'q' to quit after you've read the terms."), nil 247 } 248 return nil, errors.NotFoundf("pager") 249 } 250 251 func userAgrees(input string) bool { 252 if input == "y" || input == "Y" || input == "" { 253 return true 254 } 255 return false 256 }