github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/cmd/modelcmd/controller_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package modelcmd_test 5 6 import ( 7 "os" 8 "regexp" 9 "strings" 10 11 "github.com/juju/cmd/v3" 12 "github.com/juju/cmd/v3/cmdtesting" 13 "github.com/juju/errors" 14 "github.com/juju/testing" 15 jc "github.com/juju/testing/checkers" 16 gc "gopkg.in/check.v1" 17 18 jujucmd "github.com/juju/juju/cmd" 19 "github.com/juju/juju/cmd/modelcmd" 20 "github.com/juju/juju/juju/osenv" 21 "github.com/juju/juju/jujuclient" 22 coretesting "github.com/juju/juju/testing" 23 ) 24 25 type ControllerCommandSuite struct { 26 testing.IsolationSuite 27 } 28 29 var _ = gc.Suite(&ControllerCommandSuite{}) 30 31 func (s *ControllerCommandSuite) TestControllerCommandNoneSpecified(c *gc.C) { 32 command, err := runTestControllerCommand(c, jujuclient.NewMemStore()) 33 c.Assert(err, jc.ErrorIsNil) 34 controllerName, err := command.ControllerName() 35 c.Assert(errors.Cause(err), gc.Equals, modelcmd.ErrNoControllersDefined) 36 c.Assert(controllerName, gc.Equals, "") 37 } 38 39 func (s *ControllerCommandSuite) TestCurrentControllerFromControllerEnvVar(c *gc.C) { 40 s.PatchEnvironment("JUJU_CONTROLLER", "bar") 41 store := jujuclient.NewMemStore() 42 store.Controllers["bar"] = jujuclient.ControllerDetails{} 43 testEnsureControllerName(c, store, "bar") 44 } 45 46 func (s *ControllerCommandSuite) TestCurrentControllerFromModelEnvVar(c *gc.C) { 47 s.PatchEnvironment("JUJU_MODEL", "buzz:bar") 48 store := jujuclient.NewMemStore() 49 store.Controllers["buzz"] = jujuclient.ControllerDetails{} 50 testEnsureControllerName(c, store, "buzz") 51 } 52 53 func (s *ControllerCommandSuite) TestCurrentControllerFromStore(c *gc.C) { 54 store := jujuclient.NewMemStore() 55 store.CurrentControllerName = "foo" 56 store.Controllers["foo"] = jujuclient.ControllerDetails{} 57 testEnsureControllerName(c, store, "foo") 58 } 59 60 func (s *ControllerCommandSuite) TestCurrentControllerEnvVarConflict(c *gc.C) { 61 s.PatchEnvironment("JUJU_MODEL", "buzz:bar") 62 s.PatchEnvironment("JUJU_CONTROLLER", "bar") 63 store := jujuclient.NewMemStore() 64 store.CurrentControllerName = "foo" 65 store.Controllers["buzz"] = jujuclient.ControllerDetails{} 66 store.Controllers["foo"] = jujuclient.ControllerDetails{} 67 store.Controllers["bar"] = jujuclient.ControllerDetails{} 68 command, err := runTestControllerCommand(c, store) 69 c.Assert(err, jc.ErrorIsNil) 70 _, err = command.ControllerName() 71 c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta("controller name from JUJU_MODEL (buzz) conflicts with value in JUJU_CONTROLLER (bar)")) 72 } 73 74 func (s *ControllerCommandSuite) TestCurrentControllerPrecedenceEnvVar(c *gc.C) { 75 s.PatchEnvironment("JUJU_CONTROLLER", "bar") 76 store := jujuclient.NewMemStore() 77 store.CurrentControllerName = "foo" 78 store.Controllers["foo"] = jujuclient.ControllerDetails{} 79 store.Controllers["bar"] = jujuclient.ControllerDetails{} 80 testEnsureControllerName(c, store, "bar") 81 } 82 83 func (s *ControllerCommandSuite) TesCurrentControllerDeterminedButNotInStore(c *gc.C) { 84 s.PatchEnvironment("JUJU_CONTROLLER", "bar") 85 _, err := runTestControllerCommand(c, jujuclient.NewMemStore()) 86 c.Assert(err, gc.ErrorMatches, "controller bar not found") 87 } 88 89 func (s *ControllerCommandSuite) TestControllerCommandInitExplicit(c *gc.C) { 90 // Take controller name from command line arg, and it trumps the current- 91 // controller file. 92 store := jujuclient.NewMemStore() 93 store.CurrentControllerName = "foo" 94 store.Accounts["explicit"] = jujuclient.AccountDetails{ 95 User: "bar", 96 } 97 store.Controllers["explicit"] = jujuclient.ControllerDetails{} 98 testEnsureControllerName(c, store, "explicit", "-c", "explicit") 99 testEnsureControllerName(c, store, "explicit", "--controller", "explicit") 100 os.Setenv(osenv.JujuControllerEnvKey, "explicit") 101 testEnsureControllerName(c, store, "explicit") 102 } 103 104 func (s *ControllerCommandSuite) TestWrapWithoutFlags(c *gc.C) { 105 command := new(testControllerCommand) 106 wrapped := modelcmd.WrapController(command, modelcmd.WrapControllerSkipControllerFlags) 107 err := cmdtesting.InitCommand(wrapped, []string{"-s", "testsys"}) 108 c.Assert(err, gc.ErrorMatches, "option provided but not defined: -s") 109 } 110 111 func (s *ControllerCommandSuite) TestInnerCommand(c *gc.C) { 112 command := new(testControllerCommand) 113 wrapped := modelcmd.WrapController(command) 114 c.Assert(modelcmd.InnerCommand(wrapped), gc.Equals, command) 115 } 116 117 type testControllerCommand struct { 118 modelcmd.ControllerCommandBase 119 } 120 121 func (c *testControllerCommand) Info() *cmd.Info { 122 return jujucmd.Info(&cmd.Info{ 123 Name: "testControllerCommand", 124 FlagKnownAs: "option", 125 }) 126 } 127 128 func (c *testControllerCommand) Run(ctx *cmd.Context) error { 129 return nil 130 } 131 132 func testEnsureControllerName(c *gc.C, store jujuclient.ClientStore, expect string, args ...string) { 133 command, err := runTestControllerCommand(c, store, args...) 134 c.Assert(err, jc.ErrorIsNil) 135 controllerName, err := command.ControllerName() 136 c.Assert(err, jc.ErrorIsNil) 137 c.Assert(controllerName, gc.Equals, expect) 138 } 139 140 func runTestControllerCommand(c *gc.C, store jujuclient.ClientStore, args ...string) (modelcmd.ControllerCommand, error) { 141 command := modelcmd.WrapController(new(testControllerCommand)) 142 command.SetClientStore(store) 143 _, err := cmdtesting.RunCommand(c, command, args...) 144 return command, errors.Trace(err) 145 } 146 147 type OptionalControllerCommandSuite struct { 148 testing.IsolationSuite 149 coretesting.JujuOSEnvSuite 150 } 151 152 var _ = gc.Suite(&OptionalControllerCommandSuite{}) 153 154 func (s *OptionalControllerCommandSuite) SetUpTest(c *gc.C) { 155 s.IsolationSuite.SetUpTest(c) 156 s.JujuOSEnvSuite.SetUpTest(c) 157 } 158 159 func (s *OptionalControllerCommandSuite) TearDownTest(c *gc.C) { 160 s.IsolationSuite.TearDownTest(c) 161 s.JujuOSEnvSuite.TearDownTest(c) 162 } 163 164 func (s *OptionalControllerCommandSuite) TestEmbedded(c *gc.C) { 165 optCommand := modelcmd.OptionalControllerCommand{} 166 optCommand.Embedded = true 167 command := &testOptionalControllerCommand{OptionalControllerCommand: optCommand} 168 _, err := cmdtesting.RunCommand(c, command, "--client") 169 c.Assert(err, gc.ErrorMatches, `option provided but not defined: --client`) 170 } 171 172 func (s *OptionalControllerCommandSuite) assertPrompt(c *gc.C, 173 store jujuclient.ClientStore, 174 action string, 175 userAnswer string, 176 in ...string, 177 ) (*cmd.Context, *testOptionalControllerCommand, error) { 178 ctx, command, err := runOptionalControllerCommand(c, store, in...) 179 c.Assert(err, jc.ErrorIsNil) 180 ctx.Stdin = strings.NewReader(userAnswer) 181 err = command.MaybePrompt(ctx, action) 182 return ctx, command, err 183 } 184 185 type testData struct { 186 action string 187 userAnswer string 188 expectedPrompt string 189 expectedInfo string 190 expectedControllerName string 191 expectedClientOperation bool 192 args []string 193 } 194 195 func (s *OptionalControllerCommandSuite) assertPrompted(c *gc.C, store jujuclient.ClientStore, t testData) { 196 ctx, command, err := s.assertPrompt(c, store, t.action, t.userAnswer, t.args...) 197 c.Assert(err, jc.ErrorIsNil) 198 c.Assert(command.ControllerName, gc.Equals, t.expectedControllerName) 199 c.Assert(command.Client, gc.Equals, t.expectedClientOperation) 200 c.Assert(cmdtesting.Stdout(ctx), gc.Equals, t.expectedPrompt) 201 c.Assert(cmdtesting.Stderr(ctx), gc.Equals, t.expectedInfo) 202 } 203 204 func (s *OptionalControllerCommandSuite) TestPromptManyControllersNoCurrent(c *gc.C) { 205 store := jujuclient.NewMemStore() 206 store.Controllers = map[string]jujuclient.ControllerDetails{ 207 "fred": {}, 208 "mary": {}, 209 } 210 s.assertPrompted(c, store, testData{ 211 userAnswer: "y\n", 212 expectedPrompt: "Do you ONLY want to this client? (Y/n): \n", 213 expectedInfo: "This operation can be applied to both a copy on this client and to the one on a controller.\n" + 214 "No current controller was detected but there are other controllers registered: use -c or --controller to specify a controller if needed.\n", 215 expectedControllerName: "", 216 expectedClientOperation: true, 217 }) 218 } 219 220 func (s *OptionalControllerCommandSuite) TestPromptNoRegisteredControllers(c *gc.C) { 221 // Since there are no controllers registered on the client, the operation is 222 // assumed to be desired only on the client. 223 s.assertPrompted(c, jujuclient.NewMemStore(), testData{ 224 userAnswer: "n\n", 225 expectedPrompt: "", 226 expectedInfo: "This operation can be applied to both a copy on this client and to the one on a controller.\n" + 227 "No current controller was detected and there are no registered controllers on this client: either bootstrap one or register one.\n", 228 expectedControllerName: "", 229 expectedClientOperation: true, 230 }) 231 } 232 233 func setupTestStore() jujuclient.ClientStore { 234 store := jujuclient.NewMemStore() 235 store.Controllers = map[string]jujuclient.ControllerDetails{ 236 "fred": {}, 237 } 238 store.CurrentControllerName = "fred" 239 return store 240 } 241 242 func (s *OptionalControllerCommandSuite) TestPromptDenyClientAndCurrent(c *gc.C) { 243 for _, input := range []string{"q\n", "Q\n"} { 244 s.assertPrompted(c, setupTestStore(), testData{ 245 action: "build a snowman on", 246 expectedInfo: "This operation can be applied to both a copy on this client and to the one on a controller.\n" + 247 "Neither client nor controller specified - nothing to do.\n", 248 expectedPrompt: ` 249 Do you want to build a snowman on: 250 1. client only (--client) 251 2. controller "fred" only (--controller fred) 252 3. both (--client --controller fred) 253 Enter your choice, or type Q|q to quit: `[1:], 254 userAnswer: input, 255 expectedControllerName: "", 256 expectedClientOperation: false, 257 }) 258 } 259 } 260 261 func (s *OptionalControllerCommandSuite) TestPromptInvalidChoice(c *gc.C) { 262 s.assertPrompted(c, setupTestStore(), testData{ 263 action: "build a snowman on", 264 expectedInfo: "This operation can be applied to both a copy on this client and to the one on a controller.\n" + 265 "Invalid choice, enter a number between 1 and 3 or quit Q|q\n" + 266 "Neither client nor controller specified - nothing to do.\n", 267 expectedPrompt: ` 268 Do you want to build a snowman on: 269 1. client only (--client) 270 2. controller "fred" only (--controller fred) 271 3. both (--client --controller fred) 272 Enter your choice, or type Q|q to quit: `[1:], 273 userAnswer: "5\nq\n", 274 expectedControllerName: "", 275 expectedClientOperation: false, 276 }) 277 } 278 279 func (s *OptionalControllerCommandSuite) TestPromptConfirmClient(c *gc.C) { 280 s.assertPrompted(c, setupTestStore(), testData{ 281 action: "build a snowman on", 282 expectedInfo: "This operation can be applied to both a copy on this client and to the one on a controller.\n", 283 expectedPrompt: ` 284 Do you want to build a snowman on: 285 1. client only (--client) 286 2. controller "fred" only (--controller fred) 287 3. both (--client --controller fred) 288 Enter your choice, or type Q|q to quit: `[1:], 289 userAnswer: "1\n", 290 expectedControllerName: "", 291 expectedClientOperation: true, 292 }) 293 } 294 295 func (s *OptionalControllerCommandSuite) TestPromptConfirmController(c *gc.C) { 296 s.assertPrompted(c, setupTestStore(), testData{ 297 action: "build a snowman on", 298 expectedInfo: "This operation can be applied to both a copy on this client and to the one on a controller.\n", 299 expectedPrompt: ` 300 Do you want to build a snowman on: 301 1. client only (--client) 302 2. controller "fred" only (--controller fred) 303 3. both (--client --controller fred) 304 Enter your choice, or type Q|q to quit: `[1:], 305 userAnswer: "2\n", 306 expectedControllerName: "fred", 307 expectedClientOperation: false, 308 }) 309 } 310 311 func (s *OptionalControllerCommandSuite) TestPromptConfirmBoth(c *gc.C) { 312 s.assertPrompted(c, setupTestStore(), testData{ 313 action: "build a snowman on", 314 expectedInfo: "This operation can be applied to both a copy on this client and to the one on a controller.\n", 315 expectedPrompt: ` 316 Do you want to build a snowman on: 317 1. client only (--client) 318 2. controller "fred" only (--controller fred) 319 3. both (--client --controller fred) 320 Enter your choice, or type Q|q to quit: `[1:], 321 userAnswer: "3\n", 322 expectedControllerName: "fred", 323 expectedClientOperation: true, 324 }) 325 } 326 327 func (s *OptionalControllerCommandSuite) assertNoPromptForReadOnlyCommands(c *gc.C, store jujuclient.ClientStore, expectedErr, expectedOut, expectedController string) { 328 command := &testOptionalControllerCommand{ 329 OptionalControllerCommand: modelcmd.OptionalControllerCommand{Store: store, ReadOnly: true}, 330 } 331 ctx, err := cmdtesting.RunCommand(c, command) 332 c.Assert(err, jc.ErrorIsNil) 333 err = command.MaybePrompt(ctx, "add a cloud") 334 c.Assert(err, jc.ErrorIsNil) 335 c.Assert(cmdtesting.Stdout(ctx), gc.Equals, expectedOut) 336 c.Assert(cmdtesting.Stderr(ctx), gc.Equals, expectedErr) 337 c.Assert(command.ControllerName, gc.Equals, expectedController) 338 c.Assert(command.Client, jc.IsTrue) 339 340 } 341 342 func (s *OptionalControllerCommandSuite) TestNoPromptForReadOnlyNoCurrentController(c *gc.C) { 343 s.assertNoPromptForReadOnlyCommands(c, jujuclient.NewMemStore(), "", "", "") 344 } 345 346 func (s *OptionalControllerCommandSuite) TestNoPromptForReadOnlyWithCurrentController(c *gc.C) { 347 s.assertNoPromptForReadOnlyCommands(c, setupTestStore(), "", "", "fred") 348 } 349 350 type testOptionalControllerCommand struct { 351 modelcmd.OptionalControllerCommand 352 } 353 354 func (c *testOptionalControllerCommand) Info() *cmd.Info { 355 return jujucmd.Info(&cmd.Info{ 356 Name: "testOptionalControllerCommand", 357 FlagKnownAs: "option", 358 }) 359 } 360 361 func (c *testOptionalControllerCommand) Run(ctx *cmd.Context) error { 362 return nil 363 } 364 365 func runOptionalControllerCommand(c *gc.C, store jujuclient.ClientStore, args ...string) (*cmd.Context, *testOptionalControllerCommand, error) { 366 optCommand := modelcmd.OptionalControllerCommand{Store: store} 367 command := &testOptionalControllerCommand{OptionalControllerCommand: optCommand} 368 ctx, err := cmdtesting.RunCommand(c, command, args...) 369 return ctx, command, errors.Trace(err) 370 }