github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/cmd/juju/controller/register_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package controller_test
     5  
     6  import (
     7  	"encoding/asn1"
     8  	"encoding/base64"
     9  	"encoding/json"
    10  	"io"
    11  	"io/ioutil"
    12  	"net/http"
    13  	"net/http/httptest"
    14  	"net/url"
    15  	"strings"
    16  
    17  	"github.com/juju/cmd"
    18  	"github.com/juju/errors"
    19  	jc "github.com/juju/testing/checkers"
    20  	"golang.org/x/crypto/nacl/secretbox"
    21  	gc "gopkg.in/check.v1"
    22  	"gopkg.in/juju/names.v2"
    23  
    24  	"github.com/juju/juju/api"
    25  	"github.com/juju/juju/api/base"
    26  	"github.com/juju/juju/apiserver/params"
    27  	"github.com/juju/juju/cmd/juju/controller"
    28  	cmdtesting "github.com/juju/juju/cmd/testing"
    29  	"github.com/juju/juju/jujuclient"
    30  	"github.com/juju/juju/jujuclient/jujuclienttesting"
    31  	"github.com/juju/juju/testing"
    32  )
    33  
    34  type RegisterSuite struct {
    35  	testing.FakeJujuXDGDataHomeSuite
    36  	apiConnection            *mockAPIConnection
    37  	store                    *jujuclienttesting.MemStore
    38  	apiOpenError             error
    39  	listModels               func(jujuclient.ClientStore, string, string) ([]base.UserModel, error)
    40  	listModelsControllerName string
    41  	listModelsUserName       string
    42  	server                   *httptest.Server
    43  	httpHandler              http.Handler
    44  }
    45  
    46  const noModelsText = `
    47  There are no models available. You can add models with
    48  "juju add-model", or you can ask an administrator or owner
    49  of a model to grant access to that model with "juju grant".
    50  `
    51  
    52  var _ = gc.Suite(&RegisterSuite{})
    53  
    54  func (s *RegisterSuite) SetUpTest(c *gc.C) {
    55  	s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
    56  
    57  	s.apiOpenError = nil
    58  	s.httpHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
    59  	s.server = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    60  		s.httpHandler.ServeHTTP(w, r)
    61  	}))
    62  
    63  	serverURL, err := url.Parse(s.server.URL)
    64  	c.Assert(err, jc.ErrorIsNil)
    65  	s.apiConnection = &mockAPIConnection{
    66  		controllerTag: names.NewControllerTag(mockControllerUUID),
    67  		addr:          serverURL.Host,
    68  	}
    69  	s.listModelsControllerName = ""
    70  	s.listModelsUserName = ""
    71  	s.listModels = func(_ jujuclient.ClientStore, controllerName, userName string) ([]base.UserModel, error) {
    72  		s.listModelsControllerName = controllerName
    73  		s.listModelsUserName = userName
    74  		return nil, nil
    75  	}
    76  
    77  	s.store = jujuclienttesting.NewMemStore()
    78  }
    79  
    80  func (s *RegisterSuite) TearDownTest(c *gc.C) {
    81  	s.server.Close()
    82  	s.FakeJujuXDGDataHomeSuite.TearDownTest(c)
    83  }
    84  
    85  func (s *RegisterSuite) TestInit(c *gc.C) {
    86  	registerCommand := controller.NewRegisterCommandForTest(nil, nil, nil)
    87  
    88  	err := testing.InitCommand(registerCommand, []string{})
    89  	c.Assert(err, gc.ErrorMatches, "registration data missing")
    90  
    91  	err = testing.InitCommand(registerCommand, []string{"foo"})
    92  	c.Assert(err, jc.ErrorIsNil)
    93  	c.Assert(registerCommand.Arg, gc.Equals, "foo")
    94  
    95  	err = testing.InitCommand(registerCommand, []string{"foo", "bar"})
    96  	c.Assert(err, gc.ErrorMatches, `unrecognized args: \["bar"\]`)
    97  }
    98  
    99  func (s *RegisterSuite) TestRegister(c *gc.C) {
   100  	s.testRegisterSuccess(c, nil, "")
   101  	c.Assert(s.listModelsControllerName, gc.Equals, "controller-name")
   102  	c.Assert(s.listModelsUserName, gc.Equals, "bob")
   103  }
   104  
   105  func (s *RegisterSuite) TestRegisterOneModel(c *gc.C) {
   106  	s.listModels = func(_ jujuclient.ClientStore, controllerName, userName string) ([]base.UserModel, error) {
   107  		return []base.UserModel{{
   108  			Name:  "theoneandonly",
   109  			Owner: "carol",
   110  			UUID:  mockControllerUUID,
   111  		}}, nil
   112  	}
   113  	prompter := cmdtesting.NewSeqPrompter(c, "»", `
   114  Enter a new password: »hunter2
   115  
   116  Confirm password: »hunter2
   117  
   118  Initial password successfully set for bob.
   119  Enter a name for this controller \[controller-name\]: »
   120  
   121  Welcome, bob. You are now logged into "controller-name".
   122  
   123  Current model set to "carol/theoneandonly".
   124  `[1:])
   125  	s.testRegisterSuccess(c, prompter, "")
   126  	c.Assert(
   127  		s.store.Models["controller-name"].CurrentModel,
   128  		gc.Equals, "carol/theoneandonly",
   129  	)
   130  	prompter.CheckDone()
   131  }
   132  
   133  func (s *RegisterSuite) TestRegisterMultipleModels(c *gc.C) {
   134  	s.listModels = func(_ jujuclient.ClientStore, controllerName, userName string) ([]base.UserModel, error) {
   135  		return []base.UserModel{{
   136  			Name:  "model1",
   137  			Owner: "bob",
   138  			UUID:  mockControllerUUID,
   139  		}, {
   140  			Name:  "model2",
   141  			Owner: "bob",
   142  			UUID:  "eeeeeeee-12e9-11e4-8a70-b2227cce2b55",
   143  		}}, nil
   144  	}
   145  	prompter := cmdtesting.NewSeqPrompter(c, "»", `
   146  Enter a new password: »hunter2
   147  
   148  Confirm password: »hunter2
   149  
   150  Initial password successfully set for bob.
   151  Enter a name for this controller \[controller-name\]: »
   152  
   153  Welcome, bob. You are now logged into "controller-name".
   154  
   155  There are 2 models available. Use "juju switch" to select
   156  one of them:
   157    - juju switch model1
   158    - juju switch model2
   159  `[1:])
   160  	defer prompter.CheckDone()
   161  	s.testRegisterSuccess(c, prompter, "")
   162  
   163  	// When there are multiple models, no current model will be set.
   164  	// Instead, the command will output the list of models and inform
   165  	// the user how to set the current model.
   166  	_, err := s.store.CurrentModel("controller-name")
   167  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   168  }
   169  
   170  // testRegisterSuccess tests that the register command when the given
   171  // stdio instance is used for input and output. If stdio is nil, a
   172  // default prompter will be used.
   173  // If controllerName is non-empty, that name will be expected
   174  // to be the name of the registered controller.
   175  func (s *RegisterSuite) testRegisterSuccess(c *gc.C, stdio io.ReadWriter, controllerName string) {
   176  	srv := s.mockServer(c)
   177  	s.httpHandler = srv
   178  
   179  	if controllerName == "" {
   180  		controllerName = "controller-name"
   181  	}
   182  
   183  	registrationData := s.encodeRegistrationData(c, jujuclient.RegistrationInfo{
   184  		User:           "bob",
   185  		SecretKey:      mockSecretKey,
   186  		ControllerName: "controller-name",
   187  	})
   188  	c.Logf("registration data: %q", registrationData)
   189  	if stdio == nil {
   190  		prompter := cmdtesting.NewSeqPrompter(c, "»", `
   191  Enter a new password: »hunter2
   192  
   193  Confirm password: »hunter2
   194  
   195  Initial password successfully set for bob.
   196  Enter a name for this controller \[controller-name\]: »
   197  
   198  Welcome, bob. You are now logged into "controller-name".
   199  `[1:]+noModelsText)
   200  		defer prompter.CheckDone()
   201  		stdio = prompter
   202  	}
   203  	err := s.run(c, stdio, registrationData)
   204  	c.Assert(err, jc.ErrorIsNil)
   205  
   206  	// There should have been one POST command to "/register".
   207  	c.Assert(srv.requests, gc.HasLen, 1)
   208  	c.Assert(srv.requests[0].Method, gc.Equals, "POST")
   209  	c.Assert(srv.requests[0].URL.Path, gc.Equals, "/register")
   210  	var request params.SecretKeyLoginRequest
   211  	err = json.Unmarshal(srv.requestBodies[0], &request)
   212  	c.Assert(err, jc.ErrorIsNil)
   213  	c.Assert(request.User, jc.DeepEquals, "user-bob")
   214  	c.Assert(request.Nonce, gc.HasLen, 24)
   215  	requestPayloadPlaintext, err := json.Marshal(params.SecretKeyLoginRequestPayload{
   216  		"hunter2",
   217  	})
   218  	c.Assert(err, jc.ErrorIsNil)
   219  	expectedCiphertext := s.seal(c, requestPayloadPlaintext, mockSecretKey, request.Nonce)
   220  	c.Assert(request.PayloadCiphertext, jc.DeepEquals, expectedCiphertext)
   221  
   222  	// The controller and account details should be recorded with
   223  	// the specified controller name and user
   224  	// name from the registration string.
   225  
   226  	controller, err := s.store.ControllerByName(controllerName)
   227  	c.Assert(err, jc.ErrorIsNil)
   228  	c.Assert(controller, jc.DeepEquals, &jujuclient.ControllerDetails{
   229  		ControllerUUID: mockControllerUUID,
   230  		APIEndpoints:   []string{s.apiConnection.addr},
   231  		CACert:         testing.CACert,
   232  	})
   233  	account, err := s.store.AccountDetails(controllerName)
   234  	c.Assert(err, jc.ErrorIsNil)
   235  	c.Assert(account, jc.DeepEquals, &jujuclient.AccountDetails{
   236  		User:            "bob",
   237  		LastKnownAccess: "login",
   238  	})
   239  }
   240  
   241  func (s *RegisterSuite) TestRegisterInvalidRegistrationData(c *gc.C) {
   242  	err := s.run(c, nil, "not base64")
   243  	c.Assert(err, gc.ErrorMatches, "illegal base64 data at input byte 3")
   244  
   245  	err = s.run(c, nil, "YXNuLjEK")
   246  	c.Assert(err, gc.ErrorMatches, "asn1: structure error: .*")
   247  }
   248  
   249  func (s *RegisterSuite) TestRegisterEmptyControllerName(c *gc.C) {
   250  	srv := s.mockServer(c)
   251  	s.httpHandler = srv
   252  	registrationData := s.encodeRegistrationData(c, jujuclient.RegistrationInfo{
   253  		User:      "bob",
   254  		SecretKey: mockSecretKey,
   255  	})
   256  	// We check that it loops when an empty controller name
   257  	// is entered and that the loop terminates when the user
   258  	// types ^D.
   259  	prompter := cmdtesting.NewSeqPrompter(c, "»", `
   260  Enter a new password: »hunter2
   261  
   262  Confirm password: »hunter2
   263  
   264  Initial password successfully set for bob.
   265  Enter a name for this controller: »
   266  You must specify a non-empty controller name.
   267  Enter a name for this controller: »
   268  You must specify a non-empty controller name.
   269  Enter a name for this controller: »»
   270  `[1:])
   271  	err := s.run(c, prompter, registrationData)
   272  	c.Assert(err, gc.ErrorMatches, "EOF")
   273  	prompter.AssertDone()
   274  }
   275  
   276  func (s *RegisterSuite) TestRegisterControllerNameExists(c *gc.C) {
   277  	err := s.store.AddController("controller-name", jujuclient.ControllerDetails{
   278  		ControllerUUID: "0d75314a-5266-4f4f-8523-415be76f92dc",
   279  		CACert:         testing.CACert,
   280  	})
   281  	c.Assert(err, jc.ErrorIsNil)
   282  	prompter := cmdtesting.NewSeqPrompter(c, "»", `
   283  Enter a new password: »hunter2
   284  
   285  Confirm password: »hunter2
   286  
   287  Initial password successfully set for bob.
   288  Enter a name for this controller: »controller-name
   289  Controller "controller-name" already exists.
   290  Enter a name for this controller: »other-name
   291  
   292  Welcome, bob. You are now logged into "other-name".
   293  `[1:]+noModelsText)
   294  	s.testRegisterSuccess(c, prompter, "other-name")
   295  	prompter.AssertDone()
   296  }
   297  
   298  func (s *RegisterSuite) TestControllerUUIDExists(c *gc.C) {
   299  	// Controller has the UUID from s.testRegister to mimic a user with
   300  	// this controller already registered (regardless of its name).
   301  	err := s.store.AddController("controller-name", jujuclient.ControllerDetails{
   302  		ControllerUUID: mockControllerUUID,
   303  		CACert:         testing.CACert,
   304  	})
   305  
   306  	s.listModels = func(_ jujuclient.ClientStore, controllerName, userName string) ([]base.UserModel, error) {
   307  		return []base.UserModel{{
   308  			Name:  "model-name",
   309  			Owner: "bob",
   310  			UUID:  mockControllerUUID,
   311  		}}, nil
   312  	}
   313  
   314  	registrationData := s.encodeRegistrationData(c, jujuclient.RegistrationInfo{
   315  		User:           "bob",
   316  		SecretKey:      mockSecretKey,
   317  		ControllerName: "controller-name",
   318  	})
   319  
   320  	srv := s.mockServer(c)
   321  	s.httpHandler = srv
   322  
   323  	prompter := cmdtesting.NewSeqPrompter(c, "»", `
   324  Enter a new password: »hunter2
   325  
   326  Confirm password: »hunter2
   327  
   328  Initial password successfully set for bob.
   329  `[1:])
   330  	err = s.run(c, prompter, registrationData)
   331  	c.Assert(err, gc.ErrorMatches, `controller is already registered as "controller-name"`, gc.Commentf("details: %v", errors.Details(err)))
   332  	prompter.CheckDone()
   333  }
   334  
   335  func (s *RegisterSuite) TestProposedControllerNameExists(c *gc.C) {
   336  	// Controller does not have the UUID from s.testRegister, thereby
   337  	// mimicing a user with an already registered 'foreign' controller.
   338  	err := s.store.AddController("controller-name", jujuclient.ControllerDetails{
   339  		ControllerUUID: "0d75314a-5266-4f4f-8523-415be76f92dc",
   340  		CACert:         testing.CACert,
   341  	})
   342  	c.Assert(err, jc.ErrorIsNil)
   343  
   344  	s.listModels = func(_ jujuclient.ClientStore, controllerName, userName string) ([]base.UserModel, error) {
   345  		return []base.UserModel{{
   346  			Name:  "model-name",
   347  			Owner: "bob",
   348  			UUID:  mockControllerUUID,
   349  		}}, nil
   350  	}
   351  
   352  	prompter := cmdtesting.NewSeqPrompter(c, "»", `
   353  Enter a new password: »hunter2
   354  
   355  Confirm password: »hunter2
   356  
   357  Initial password successfully set for bob.
   358  Enter a name for this controller: »controller-name
   359  Controller "controller-name" already exists.
   360  Enter a name for this controller: »other-name
   361  
   362  Welcome, bob. You are now logged into "other-name".
   363  
   364  Current model set to "bob/model-name".
   365  `[1:])
   366  	defer prompter.CheckDone()
   367  	s.testRegisterSuccess(c, prompter, "other-name")
   368  }
   369  
   370  func (s *RegisterSuite) TestRegisterEmptyPassword(c *gc.C) {
   371  	registrationData := s.encodeRegistrationData(c, jujuclient.RegistrationInfo{
   372  		User:      "bob",
   373  		SecretKey: mockSecretKey,
   374  	})
   375  	prompter := cmdtesting.NewSeqPrompter(c, "»", `
   376  Enter a new password: »
   377  
   378  `[1:])
   379  	defer prompter.CheckDone()
   380  	err := s.run(c, prompter, registrationData)
   381  	c.Assert(err, gc.ErrorMatches, "you must specify a non-empty password")
   382  }
   383  
   384  func (s *RegisterSuite) TestRegisterPasswordMismatch(c *gc.C) {
   385  	registrationData := s.encodeRegistrationData(c, jujuclient.RegistrationInfo{
   386  		User:      "bob",
   387  		SecretKey: mockSecretKey,
   388  	})
   389  	prompter := cmdtesting.NewSeqPrompter(c, "»", `
   390  Enter a new password: »hunter2
   391  
   392  Confirm password: »hunter3
   393  
   394  `[1:])
   395  	defer prompter.CheckDone()
   396  	err := s.run(c, prompter, registrationData)
   397  	c.Assert(err, gc.ErrorMatches, "passwords do not match")
   398  }
   399  
   400  func (s *RegisterSuite) TestAPIOpenError(c *gc.C) {
   401  	registrationData := s.encodeRegistrationData(c, jujuclient.RegistrationInfo{
   402  		User:      "bob",
   403  		SecretKey: mockSecretKey,
   404  	})
   405  	prompter := cmdtesting.NewSeqPrompter(c, "»", `
   406  Enter a new password: »hunter2
   407  
   408  Confirm password: »hunter2
   409  
   410  `[1:])
   411  	defer prompter.CheckDone()
   412  	s.apiOpenError = errors.New("open failed")
   413  	err := s.run(c, prompter, registrationData)
   414  	c.Assert(err, gc.ErrorMatches, `open failed`)
   415  }
   416  
   417  func (s *RegisterSuite) TestRegisterServerError(c *gc.C) {
   418  	response, err := json.Marshal(params.ErrorResult{
   419  		Error: &params.Error{Message: "xyz", Code: "123"},
   420  	})
   421  
   422  	s.httpHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   423  		w.WriteHeader(http.StatusInternalServerError)
   424  		_, err = w.Write(response)
   425  		c.Check(err, jc.ErrorIsNil)
   426  	})
   427  	prompter := cmdtesting.NewSeqPrompter(c, "»", `
   428  Enter a new password: »hunter2
   429  
   430  Confirm password: »hunter2
   431  
   432  `[1:])
   433  
   434  	registrationData := s.encodeRegistrationData(c, jujuclient.RegistrationInfo{
   435  		User:      "bob",
   436  		SecretKey: mockSecretKey,
   437  	})
   438  	err = s.run(c, prompter, registrationData)
   439  	c.Assert(err, gc.ErrorMatches, "xyz")
   440  
   441  	// Check that the controller hasn't been added.
   442  	_, err = s.store.ControllerByName("controller-name")
   443  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   444  }
   445  
   446  func (s *RegisterSuite) TestRegisterPublic(c *gc.C) {
   447  	s.apiConnection.authTag = names.NewUserTag("bob@external")
   448  	s.apiConnection.controllerAccess = "login"
   449  	prompter := cmdtesting.NewSeqPrompter(c, "»", `
   450  Enter a name for this controller: »public-controller-name
   451  
   452  Welcome, bob@external. You are now logged into "public-controller-name".
   453  `[1:]+noModelsText)
   454  	defer prompter.CheckDone()
   455  	err := s.run(c, prompter, "0.1.2.3")
   456  	c.Assert(err, jc.ErrorIsNil)
   457  
   458  	// The controller and account details should be recorded with
   459  	// the specified controller name and user
   460  	// name from the auth tag.
   461  
   462  	controller, err := s.store.ControllerByName("public-controller-name")
   463  	c.Assert(err, jc.ErrorIsNil)
   464  	c.Assert(controller, jc.DeepEquals, &jujuclient.ControllerDetails{
   465  		ControllerUUID: mockControllerUUID,
   466  		APIEndpoints:   []string{"0.1.2.3:443"},
   467  	})
   468  	account, err := s.store.AccountDetails("public-controller-name")
   469  	c.Assert(err, jc.ErrorIsNil)
   470  	c.Assert(account, jc.DeepEquals, &jujuclient.AccountDetails{
   471  		User:            "bob@external",
   472  		LastKnownAccess: "login",
   473  	})
   474  }
   475  
   476  func (s *RegisterSuite) TestRegisterPublicAPIOpenError(c *gc.C) {
   477  	s.apiOpenError = errors.New("open failed")
   478  	err := s.run(c, noPrompts(c), "0.1.2.3")
   479  	c.Assert(err, gc.ErrorMatches, `open failed`)
   480  }
   481  
   482  func (s *RegisterSuite) TestRegisterPublicWithPort(c *gc.C) {
   483  	s.apiConnection.authTag = names.NewUserTag("bob@external")
   484  	s.apiConnection.controllerAccess = "login"
   485  	prompter := cmdtesting.NewSeqPrompter(c, "»", `
   486  Enter a name for this controller: »public-controller-name
   487  
   488  Welcome, bob@external. You are now logged into "public-controller-name".
   489  `[1:]+noModelsText)
   490  	defer prompter.CheckDone()
   491  	err := s.run(c, prompter, "0.1.2.3:5678")
   492  	c.Assert(err, jc.ErrorIsNil)
   493  
   494  	// The controller and account details should be recorded with
   495  	// the specified controller name and user
   496  	// name from the auth tag.
   497  
   498  	controller, err := s.store.ControllerByName("public-controller-name")
   499  	c.Assert(err, jc.ErrorIsNil)
   500  	c.Assert(controller, jc.DeepEquals, &jujuclient.ControllerDetails{
   501  		ControllerUUID: mockControllerUUID,
   502  		APIEndpoints:   []string{"0.1.2.3:5678"},
   503  	})
   504  }
   505  
   506  type mockServer struct {
   507  	requests      []*http.Request
   508  	requestBodies [][]byte
   509  	response      []byte
   510  }
   511  
   512  const mockControllerUUID = "df136476-12e9-11e4-8a70-b2227cce2b54"
   513  
   514  var mockSecretKey = []byte(strings.Repeat("X", 32))
   515  
   516  // mockServer returns a mock HTTP server that will always respond with a
   517  // response encoded with mockSecretKey and a constant nonce, containing
   518  // testing.CACert and mockControllerUUID.
   519  //
   520  // Each time a call is made, the requests and requestBodies fields in
   521  // the returned mockServer instance are appended with the request details.
   522  func (s *RegisterSuite) mockServer(c *gc.C) *mockServer {
   523  	respNonce := []byte(strings.Repeat("X", 24))
   524  
   525  	responsePayloadPlaintext, err := json.Marshal(params.SecretKeyLoginResponsePayload{
   526  		CACert:         testing.CACert,
   527  		ControllerUUID: mockControllerUUID,
   528  	})
   529  	c.Assert(err, jc.ErrorIsNil)
   530  	response, err := json.Marshal(params.SecretKeyLoginResponse{
   531  		Nonce:             respNonce,
   532  		PayloadCiphertext: s.seal(c, responsePayloadPlaintext, mockSecretKey, respNonce),
   533  	})
   534  	c.Assert(err, jc.ErrorIsNil)
   535  	return &mockServer{
   536  		response: response,
   537  	}
   538  }
   539  
   540  func (s *RegisterSuite) encodeRegistrationData(c *gc.C, info jujuclient.RegistrationInfo) string {
   541  	info.Addrs = []string{s.apiConnection.addr}
   542  	data, err := asn1.Marshal(info)
   543  	c.Assert(err, jc.ErrorIsNil)
   544  	// Append some junk to the end of the encoded data to
   545  	// ensure that, if we have to pad the data in add-user,
   546  	// register can still decode it.
   547  	data = append(data, 0, 0, 0)
   548  	return base64.URLEncoding.EncodeToString(data)
   549  }
   550  
   551  func (srv *mockServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   552  	srv.requests = append(srv.requests, r)
   553  	requestBody, err := ioutil.ReadAll(r.Body)
   554  	if err != nil {
   555  		panic(err)
   556  	}
   557  	srv.requestBodies = append(srv.requestBodies, requestBody)
   558  	_, err = w.Write(srv.response)
   559  	if err != nil {
   560  		panic(err)
   561  	}
   562  }
   563  
   564  func (s *RegisterSuite) apiOpen(info *api.Info, opts api.DialOpts) (api.Connection, error) {
   565  	if s.apiOpenError != nil {
   566  		return nil, s.apiOpenError
   567  	}
   568  	return s.apiConnection, nil
   569  }
   570  
   571  func (s *RegisterSuite) run(c *gc.C, stdio io.ReadWriter, args ...string) error {
   572  	if stdio == nil {
   573  		p := noPrompts(c)
   574  		stdio = p
   575  		defer p.CheckDone()
   576  	}
   577  
   578  	command := controller.NewRegisterCommandForTest(s.apiOpen, s.listModels, s.store)
   579  	err := testing.InitCommand(command, args)
   580  	c.Assert(err, jc.ErrorIsNil)
   581  	return command.Run(&cmd.Context{
   582  		Dir:    c.MkDir(),
   583  		Stdin:  stdio,
   584  		Stdout: stdio,
   585  		Stderr: stdio,
   586  	})
   587  }
   588  
   589  func noPrompts(c *gc.C) *cmdtesting.SeqPrompter {
   590  	return cmdtesting.NewSeqPrompter(c, "»", "")
   591  }
   592  
   593  func (s *RegisterSuite) seal(c *gc.C, message, key, nonce []byte) []byte {
   594  	var keyArray [32]byte
   595  	var nonceArray [24]byte
   596  	c.Assert(copy(keyArray[:], key), gc.Equals, len(keyArray))
   597  	c.Assert(copy(nonceArray[:], nonce), gc.Equals, len(nonceArray))
   598  	return secretbox.Seal(nil, message, &nonceArray, &keyArray)
   599  }