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  }