github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/commands/bootstrap_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package commands
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"path/filepath"
    12  	"runtime"
    13  	"sort"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/juju/cmd"
    18  	"github.com/juju/errors"
    19  	"github.com/juju/testing"
    20  	jc "github.com/juju/testing/checkers"
    21  	"github.com/juju/utils"
    22  	"github.com/juju/utils/arch"
    23  	jujuos "github.com/juju/utils/os"
    24  	"github.com/juju/utils/series"
    25  	"github.com/juju/version"
    26  	gc "gopkg.in/check.v1"
    27  
    28  	"github.com/juju/juju/cloud"
    29  	"github.com/juju/juju/cmd/modelcmd"
    30  	cmdtesting "github.com/juju/juju/cmd/testing"
    31  	"github.com/juju/juju/constraints"
    32  	"github.com/juju/juju/environs"
    33  	"github.com/juju/juju/environs/bootstrap"
    34  	"github.com/juju/juju/environs/config"
    35  	"github.com/juju/juju/environs/filestorage"
    36  	"github.com/juju/juju/environs/gui"
    37  	"github.com/juju/juju/environs/imagemetadata"
    38  	"github.com/juju/juju/environs/simplestreams"
    39  	sstesting "github.com/juju/juju/environs/simplestreams/testing"
    40  	"github.com/juju/juju/environs/sync"
    41  	envtesting "github.com/juju/juju/environs/testing"
    42  	envtools "github.com/juju/juju/environs/tools"
    43  	toolstesting "github.com/juju/juju/environs/tools/testing"
    44  	"github.com/juju/juju/instance"
    45  	"github.com/juju/juju/juju/keys"
    46  	"github.com/juju/juju/juju/osenv"
    47  	"github.com/juju/juju/jujuclient"
    48  	"github.com/juju/juju/jujuclient/jujuclienttesting"
    49  	"github.com/juju/juju/network"
    50  	"github.com/juju/juju/provider/dummy"
    51  	coretesting "github.com/juju/juju/testing"
    52  	coretools "github.com/juju/juju/tools"
    53  	jujuversion "github.com/juju/juju/version"
    54  )
    55  
    56  type BootstrapSuite struct {
    57  	coretesting.FakeJujuXDGDataHomeSuite
    58  	testing.MgoSuite
    59  	envtesting.ToolsFixture
    60  	store *jujuclienttesting.MemStore
    61  }
    62  
    63  var _ = gc.Suite(&BootstrapSuite{})
    64  
    65  func init() {
    66  	dummyProvider, err := environs.Provider("dummy")
    67  	if err != nil {
    68  		panic(err)
    69  	}
    70  	environs.RegisterProvider("no-cloud-region-detection", noCloudRegionDetectionProvider{})
    71  	environs.RegisterProvider("no-cloud-regions", noCloudRegionsProvider{dummyProvider})
    72  	environs.RegisterProvider("no-credentials", noCredentialsProvider{})
    73  	environs.RegisterProvider("many-credentials", manyCredentialsProvider{dummyProvider})
    74  }
    75  
    76  func (s *BootstrapSuite) SetUpSuite(c *gc.C) {
    77  	s.FakeJujuXDGDataHomeSuite.SetUpSuite(c)
    78  	s.MgoSuite.SetUpSuite(c)
    79  	s.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey)
    80  }
    81  
    82  func (s *BootstrapSuite) SetUpTest(c *gc.C) {
    83  	s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
    84  	s.MgoSuite.SetUpTest(c)
    85  	s.ToolsFixture.SetUpTest(c)
    86  
    87  	// Set jujuversion.Current to a known value, for which we
    88  	// will make tools available. Individual tests may
    89  	// override this.
    90  	s.PatchValue(&jujuversion.Current, v100p64.Number)
    91  	s.PatchValue(&arch.HostArch, func() string { return v100p64.Arch })
    92  	s.PatchValue(&series.HostSeries, func() string { return v100p64.Series })
    93  	s.PatchValue(&jujuos.HostOS, func() jujuos.OSType { return jujuos.Ubuntu })
    94  
    95  	// Set up a local source with tools.
    96  	sourceDir := createToolsSource(c, vAll)
    97  	s.PatchValue(&envtools.DefaultBaseURL, sourceDir)
    98  
    99  	expectedNumber := jujuversion.Current
   100  	expectedNumber.Build = 1235
   101  	s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c, &expectedNumber))
   102  
   103  	s.PatchValue(&waitForAgentInitialisation, func(*cmd.Context, *modelcmd.ModelCommandBase, string, string) error {
   104  		return nil
   105  	})
   106  
   107  	// TODO(wallyworld) - add test data when tests are improved
   108  	s.store = jujuclienttesting.NewMemStore()
   109  }
   110  
   111  func (s *BootstrapSuite) TearDownSuite(c *gc.C) {
   112  	s.MgoSuite.TearDownSuite(c)
   113  	s.FakeJujuXDGDataHomeSuite.TearDownSuite(c)
   114  }
   115  
   116  func (s *BootstrapSuite) TearDownTest(c *gc.C) {
   117  	s.ToolsFixture.TearDownTest(c)
   118  	s.MgoSuite.TearDownTest(c)
   119  	s.FakeJujuXDGDataHomeSuite.TearDownTest(c)
   120  	dummy.Reset(c)
   121  }
   122  
   123  // bootstrapCommandWrapper wraps the bootstrap command. The wrapped command has
   124  // the ability to disable fetching GUI information from simplestreams, so that
   125  // it is possible to test the bootstrap process without connecting to the
   126  // network. This ability can be turned on by setting disableGUI to true.
   127  type bootstrapCommandWrapper struct {
   128  	bootstrapCommand
   129  	disableGUI bool
   130  }
   131  
   132  func (c *bootstrapCommandWrapper) Run(ctx *cmd.Context) error {
   133  	if c.disableGUI {
   134  		c.bootstrapCommand.noGUI = true
   135  	}
   136  	return c.bootstrapCommand.Run(ctx)
   137  }
   138  
   139  func (s *BootstrapSuite) newBootstrapCommand() cmd.Command {
   140  	return s.newBootstrapCommandWrapper(true)
   141  }
   142  
   143  func (s *BootstrapSuite) newBootstrapCommandWrapper(disableGUI bool) cmd.Command {
   144  	c := &bootstrapCommandWrapper{
   145  		disableGUI: disableGUI,
   146  	}
   147  	c.SetClientStore(s.store)
   148  	return modelcmd.Wrap(c)
   149  }
   150  
   151  func (s *BootstrapSuite) TestRunTests(c *gc.C) {
   152  	for i, test := range bootstrapTests {
   153  		c.Logf("\ntest %d: %s", i, test.info)
   154  		restore := s.run(c, test)
   155  		restore()
   156  	}
   157  }
   158  
   159  type bootstrapTest struct {
   160  	info string
   161  	// binary version string used to set jujuversion.Current
   162  	version string
   163  	sync    bool
   164  	args    []string
   165  	err     string
   166  	// binary version string for expected tools; if set, no default tools
   167  	// will be uploaded before running the test.
   168  	upload               string
   169  	constraints          constraints.Value
   170  	bootstrapConstraints constraints.Value
   171  	placement            string
   172  	hostArch             string
   173  	keepBroken           bool
   174  }
   175  
   176  func (s *BootstrapSuite) patchVersionAndSeries(c *gc.C, hostSeries string) {
   177  	resetJujuXDGDataHome(c)
   178  	s.PatchValue(&series.HostSeries, func() string { return hostSeries })
   179  	s.patchVersion(c)
   180  }
   181  
   182  func (s *BootstrapSuite) patchVersion(c *gc.C) {
   183  	// Force a dev version by having a non zero build number.
   184  	// This is because we have not uploaded any tools and auto
   185  	// upload is only enabled for dev versions.
   186  	num := jujuversion.Current
   187  	num.Build = 1234
   188  	s.PatchValue(&jujuversion.Current, num)
   189  }
   190  
   191  func (s *BootstrapSuite) run(c *gc.C, test bootstrapTest) testing.Restorer {
   192  	// Create home with dummy provider and remove all
   193  	// of its envtools.
   194  	resetJujuXDGDataHome(c)
   195  	dummy.Reset(c)
   196  
   197  	var restore testing.Restorer = func() {
   198  		s.store = jujuclienttesting.NewMemStore()
   199  	}
   200  	bootstrapVersion := v100p64
   201  	if test.version != "" {
   202  		useVersion := strings.Replace(test.version, "%LTS%", series.LatestLts(), 1)
   203  		bootstrapVersion = version.MustParseBinary(useVersion)
   204  		restore = restore.Add(testing.PatchValue(&jujuversion.Current, bootstrapVersion.Number))
   205  		restore = restore.Add(testing.PatchValue(&arch.HostArch, func() string { return bootstrapVersion.Arch }))
   206  		restore = restore.Add(testing.PatchValue(&series.HostSeries, func() string { return bootstrapVersion.Series }))
   207  		bootstrapVersion.Build = 1
   208  		if test.upload != "" {
   209  			uploadVers := version.MustParseBinary(test.upload)
   210  			bootstrapVersion.Number = uploadVers.Number
   211  		}
   212  		restore = restore.Add(testing.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c, &bootstrapVersion.Number)))
   213  	}
   214  
   215  	if test.hostArch != "" {
   216  		restore = restore.Add(testing.PatchValue(&arch.HostArch, func() string { return test.hostArch }))
   217  	}
   218  
   219  	controllerName := "peckham-controller"
   220  	cloudName := "dummy"
   221  
   222  	// Run command and check for uploads.
   223  	args := append([]string{
   224  		controllerName, cloudName,
   225  		"--config", "default-series=raring",
   226  	}, test.args...)
   227  	opc, errc := cmdtesting.RunCommand(cmdtesting.NullContext(c), s.newBootstrapCommand(), args...)
   228  	var err error
   229  	select {
   230  	case err = <-errc:
   231  	case <-time.After(coretesting.LongWait):
   232  		c.Fatal("timed out")
   233  	}
   234  	// Check for remaining operations/errors.
   235  	if test.err != "" {
   236  		c.Assert(err, gc.NotNil)
   237  		stripped := strings.Replace(err.Error(), "\n", "", -1)
   238  		c.Check(stripped, gc.Matches, test.err)
   239  		return restore
   240  	}
   241  	if !c.Check(err, gc.IsNil) {
   242  		return restore
   243  	}
   244  
   245  	op, ok := <-opc
   246  	c.Assert(ok, gc.Equals, true)
   247  	opBootstrap := op.(dummy.OpBootstrap)
   248  	c.Check(opBootstrap.Env, gc.Equals, bootstrap.ControllerModelName)
   249  	c.Check(opBootstrap.Args.ModelConstraints, gc.DeepEquals, test.constraints)
   250  	if test.bootstrapConstraints == (constraints.Value{}) {
   251  		test.bootstrapConstraints = test.constraints
   252  	}
   253  	c.Check(opBootstrap.Args.BootstrapConstraints, gc.DeepEquals, test.bootstrapConstraints)
   254  	c.Check(opBootstrap.Args.Placement, gc.Equals, test.placement)
   255  
   256  	opFinalizeBootstrap := (<-opc).(dummy.OpFinalizeBootstrap)
   257  	c.Check(opFinalizeBootstrap.Env, gc.Equals, bootstrap.ControllerModelName)
   258  	c.Check(opFinalizeBootstrap.InstanceConfig.ToolsList(), gc.Not(gc.HasLen), 0)
   259  	if test.upload != "" {
   260  		c.Check(opFinalizeBootstrap.InstanceConfig.AgentVersion().String(), gc.Equals, test.upload)
   261  	}
   262  
   263  	// Check controllers.yaml controller details.
   264  	addrConnectedTo := []string{"localhost:17070"}
   265  
   266  	controller, err := s.store.ControllerByName(controllerName)
   267  	c.Assert(err, jc.ErrorIsNil)
   268  	c.Assert(controller.CACert, gc.Not(gc.Equals), "")
   269  	c.Assert(controller.UnresolvedAPIEndpoints, gc.DeepEquals, addrConnectedTo)
   270  	c.Assert(controller.APIEndpoints, gc.DeepEquals, addrConnectedTo)
   271  	c.Assert(utils.IsValidUUIDString(controller.ControllerUUID), jc.IsTrue)
   272  	// We don't care about build numbers here.
   273  	bootstrapVers := bootstrapVersion.Number
   274  	bootstrapVers.Build = 0
   275  	controllerVers := version.MustParse(controller.AgentVersion)
   276  	controllerVers.Build = 0
   277  	c.Assert(controllerVers.String(), gc.Equals, bootstrapVers.String())
   278  
   279  	controllerModel, err := s.store.ModelByName(controllerName, "admin@local/controller")
   280  	c.Assert(err, jc.ErrorIsNil)
   281  	c.Assert(utils.IsValidUUIDString(controllerModel.ModelUUID), jc.IsTrue)
   282  
   283  	// Bootstrap config should have been saved, and should only contain
   284  	// the type, name, and any user-supplied configuration.
   285  	bootstrapConfig, err := s.store.BootstrapConfigForController(controllerName)
   286  	c.Assert(err, jc.ErrorIsNil)
   287  	c.Assert(bootstrapConfig.Cloud, gc.Equals, "dummy")
   288  	c.Assert(bootstrapConfig.Credential, gc.Equals, "")
   289  	expected := map[string]interface{}{
   290  		"name":            bootstrap.ControllerModelName,
   291  		"type":            "dummy",
   292  		"default-series":  "raring",
   293  		"authorized-keys": "public auth key\n",
   294  		// Dummy provider defaults
   295  		"broken":     "",
   296  		"secret":     "pork",
   297  		"controller": false,
   298  	}
   299  	for k, v := range config.ConfigDefaults() {
   300  		if _, ok := expected[k]; !ok {
   301  			expected[k] = v
   302  		}
   303  	}
   304  	c.Assert(bootstrapConfig.Config, jc.DeepEquals, expected)
   305  
   306  	return restore
   307  }
   308  
   309  var bootstrapTests = []bootstrapTest{{
   310  	info: "no args, no error, no upload, no constraints",
   311  }, {
   312  	info: "bad --constraints",
   313  	args: []string{"--constraints", "bad=wrong"},
   314  	err:  `unknown constraint "bad"`,
   315  }, {
   316  	info: "conflicting --constraints",
   317  	args: []string{"--constraints", "instance-type=foo mem=4G"},
   318  	err:  `ambiguous constraints: "instance-type" overlaps with "mem"`,
   319  }, {
   320  	info:    "bad model",
   321  	version: "1.2.3-%LTS%-amd64",
   322  	args:    []string{"--config", "broken=Bootstrap Destroy", "--auto-upgrade"},
   323  	err:     `failed to bootstrap model: dummy.Bootstrap is broken`,
   324  }, {
   325  	info:        "constraints",
   326  	args:        []string{"--constraints", "mem=4G cores=4"},
   327  	constraints: constraints.MustParse("mem=4G cores=4"),
   328  }, {
   329  	info:                 "bootstrap and environ constraints",
   330  	args:                 []string{"--constraints", "mem=4G cores=4", "--bootstrap-constraints", "mem=8G"},
   331  	constraints:          constraints.MustParse("mem=4G cores=4"),
   332  	bootstrapConstraints: constraints.MustParse("mem=8G cores=4"),
   333  }, {
   334  	info:        "unsupported constraint passed through but no error",
   335  	args:        []string{"--constraints", "mem=4G cores=4 cpu-power=10"},
   336  	constraints: constraints.MustParse("mem=4G cores=4 cpu-power=10"),
   337  }, {
   338  	info:        "--build-agent uses arch from constraint if it matches current version",
   339  	version:     "1.3.3-saucy-ppc64el",
   340  	hostArch:    "ppc64el",
   341  	args:        []string{"--build-agent", "--constraints", "arch=ppc64el"},
   342  	upload:      "1.3.3.1-raring-ppc64el", // from jujuversion.Current
   343  	constraints: constraints.MustParse("arch=ppc64el"),
   344  }, {
   345  	info:     "--build-agent rejects mismatched arch",
   346  	version:  "1.3.3-saucy-amd64",
   347  	hostArch: "amd64",
   348  	args:     []string{"--build-agent", "--constraints", "arch=ppc64el"},
   349  	err:      `failed to bootstrap model: cannot use agent built for "ppc64el" using a machine running on "amd64"`,
   350  }, {
   351  	info:     "--build-agent rejects non-supported arch",
   352  	version:  "1.3.3-saucy-mips64",
   353  	hostArch: "mips64",
   354  	args:     []string{"--build-agent"},
   355  	err:      fmt.Sprintf(`failed to bootstrap model: model %q of type dummy does not support instances running on "mips64"`, bootstrap.ControllerModelName),
   356  }, {
   357  	info:     "--build-agent always bumps build number",
   358  	version:  "1.2.3.4-raring-amd64",
   359  	hostArch: "amd64",
   360  	args:     []string{"--build-agent"},
   361  	upload:   "1.2.3.5-raring-amd64",
   362  }, {
   363  	info:      "placement",
   364  	args:      []string{"--to", "something"},
   365  	placement: "something",
   366  }, {
   367  	info:       "keep broken",
   368  	args:       []string{"--keep-broken"},
   369  	keepBroken: true,
   370  }, {
   371  	info: "additional args",
   372  	args: []string{"anything", "else"},
   373  	err:  `unrecognized args: \["anything" "else"\]`,
   374  }, {
   375  	info: "--agent-version with --build-agent",
   376  	args: []string{"--agent-version", "1.1.0", "--build-agent"},
   377  	err:  `--agent-version and --build-agent can't be used together`,
   378  }, {
   379  	info: "invalid --agent-version value",
   380  	args: []string{"--agent-version", "foo"},
   381  	err:  `invalid version "foo"`,
   382  }, {
   383  	info:    "agent-version doesn't match client version major",
   384  	version: "1.3.3-saucy-ppc64el",
   385  	args:    []string{"--agent-version", "2.3.0"},
   386  	err:     `requested agent version major.minor mismatch`,
   387  }, {
   388  	info:    "agent-version doesn't match client version minor",
   389  	version: "1.3.3-saucy-ppc64el",
   390  	args:    []string{"--agent-version", "1.4.0"},
   391  	err:     `requested agent version major.minor mismatch`,
   392  }, {
   393  	info: "--clouds with --regions",
   394  	args: []string{"--clouds", "--regions", "aws"},
   395  	err:  `--clouds and --regions can't be used together`,
   396  }}
   397  
   398  func (s *BootstrapSuite) TestRunCloudNameMissing(c *gc.C) {
   399  	_, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "my-controller")
   400  	c.Check(err, gc.ErrorMatches, "controller name and cloud name are required")
   401  }
   402  
   403  func (s *BootstrapSuite) TestRunCloudNameUnknown(c *gc.C) {
   404  	_, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "my-controller", "unknown")
   405  	c.Check(err, gc.ErrorMatches, `unknown cloud "unknown", please try "juju update-clouds"`)
   406  }
   407  
   408  func (s *BootstrapSuite) TestCheckProviderProvisional(c *gc.C) {
   409  	err := checkProviderType("devcontroller")
   410  	c.Assert(err, jc.ErrorIsNil)
   411  
   412  	for name, flag := range provisionalProviders {
   413  		// vsphere is disabled for gccgo. See lp:1440940.
   414  		if name == "vsphere" && runtime.Compiler == "gccgo" {
   415  			continue
   416  		}
   417  		c.Logf(" - trying %q -", name)
   418  		err := checkProviderType(name)
   419  		c.Check(err, gc.ErrorMatches, ".* provider is provisional .* set JUJU_DEV_FEATURE_FLAGS=.*")
   420  
   421  		err = os.Setenv(osenv.JujuFeatureFlagEnvKey, flag)
   422  		c.Assert(err, jc.ErrorIsNil)
   423  		err = checkProviderType(name)
   424  		c.Check(err, jc.ErrorIsNil)
   425  	}
   426  }
   427  
   428  func (s *BootstrapSuite) TestBootstrapTwice(c *gc.C) {
   429  	const controllerName = "dev"
   430  	s.patchVersionAndSeries(c, "raring")
   431  
   432  	_, err := coretesting.RunCommand(c, s.newBootstrapCommand(), controllerName, "dummy", "--auto-upgrade")
   433  	c.Assert(err, jc.ErrorIsNil)
   434  
   435  	_, err = coretesting.RunCommand(c, s.newBootstrapCommand(), controllerName, "dummy", "--auto-upgrade")
   436  	c.Assert(err, gc.ErrorMatches, `controller "dev" already exists`)
   437  }
   438  
   439  func (s *BootstrapSuite) TestBootstrapSetsCurrentModel(c *gc.C) {
   440  	s.patchVersionAndSeries(c, "raring")
   441  
   442  	_, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "devcontroller", "dummy", "--auto-upgrade")
   443  	c.Assert(err, jc.ErrorIsNil)
   444  	currentController := s.store.CurrentControllerName
   445  	c.Assert(currentController, gc.Equals, "devcontroller")
   446  	modelName, err := s.store.CurrentModel(currentController)
   447  	c.Assert(err, jc.ErrorIsNil)
   448  	c.Assert(modelName, gc.Equals, "admin@local/default")
   449  }
   450  
   451  func (s *BootstrapSuite) TestBootstrapSetsControllerDetails(c *gc.C) {
   452  	s.patchVersionAndSeries(c, "raring")
   453  
   454  	_, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "devcontroller", "dummy", "--auto-upgrade")
   455  	c.Assert(err, jc.ErrorIsNil)
   456  	currentController := s.store.CurrentControllerName
   457  	c.Assert(currentController, gc.Equals, "devcontroller")
   458  	details, err := s.store.ControllerByName(currentController)
   459  	c.Assert(err, jc.ErrorIsNil)
   460  	c.Assert(*details.ModelCount, gc.Equals, 2)
   461  	c.Assert(*details.MachineCount, gc.Equals, 1)
   462  	c.Assert(details.AgentVersion, gc.Equals, jujuversion.Current.String())
   463  }
   464  
   465  func (s *BootstrapSuite) TestBootstrapDefaultModel(c *gc.C) {
   466  	s.patchVersionAndSeries(c, "raring")
   467  
   468  	var bootstrap fakeBootstrapFuncs
   469  	s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface {
   470  		return &bootstrap
   471  	})
   472  
   473  	coretesting.RunCommand(
   474  		c, s.newBootstrapCommand(),
   475  		"devcontroller", "dummy",
   476  		"--auto-upgrade",
   477  		"--default-model", "mymodel",
   478  		"--config", "foo=bar",
   479  	)
   480  	c.Assert(utils.IsValidUUIDString(bootstrap.args.ControllerConfig.ControllerUUID()), jc.IsTrue)
   481  	c.Assert(bootstrap.args.HostedModelConfig["name"], gc.Equals, "mymodel")
   482  	c.Assert(bootstrap.args.HostedModelConfig["foo"], gc.Equals, "bar")
   483  }
   484  
   485  func (s *BootstrapSuite) TestBootstrapTimeout(c *gc.C) {
   486  	s.patchVersionAndSeries(c, "raring")
   487  
   488  	var bootstrap fakeBootstrapFuncs
   489  	s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface {
   490  		return &bootstrap
   491  	})
   492  	coretesting.RunCommand(
   493  		c, s.newBootstrapCommand(), "devcontroller", "dummy", "--auto-upgrade",
   494  		"--config", "bootstrap-timeout=99",
   495  	)
   496  	c.Assert(bootstrap.args.DialOpts.Timeout, gc.Equals, 99*time.Second)
   497  }
   498  
   499  func (s *BootstrapSuite) TestBootstrapDefaultConfigStripsProcessedAttributes(c *gc.C) {
   500  	s.patchVersionAndSeries(c, "raring")
   501  
   502  	var bootstrap fakeBootstrapFuncs
   503  	s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface {
   504  		return &bootstrap
   505  	})
   506  
   507  	fakeSSHFile := filepath.Join(c.MkDir(), "ssh")
   508  	err := ioutil.WriteFile(fakeSSHFile, []byte("ssh-key"), 0600)
   509  	c.Assert(err, jc.ErrorIsNil)
   510  	coretesting.RunCommand(
   511  		c, s.newBootstrapCommand(),
   512  		"devcontroller", "dummy",
   513  		"--auto-upgrade",
   514  		"--config", "authorized-keys-path="+fakeSSHFile,
   515  	)
   516  	_, ok := bootstrap.args.HostedModelConfig["authorized-keys-path"]
   517  	c.Assert(ok, jc.IsFalse)
   518  }
   519  
   520  func (s *BootstrapSuite) TestBootstrapDefaultConfigStripsInheritedAttributes(c *gc.C) {
   521  	s.patchVersionAndSeries(c, "raring")
   522  
   523  	var bootstrap fakeBootstrapFuncs
   524  	s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface {
   525  		return &bootstrap
   526  	})
   527  
   528  	fakeSSHFile := filepath.Join(c.MkDir(), "ssh")
   529  	err := ioutil.WriteFile(fakeSSHFile, []byte("ssh-key"), 0600)
   530  	c.Assert(err, jc.ErrorIsNil)
   531  	coretesting.RunCommand(
   532  		c, s.newBootstrapCommand(),
   533  		"devcontroller", "dummy",
   534  		"--auto-upgrade",
   535  		"--config", "authorized-keys=ssh-key",
   536  		"--config", "agent-version=1.19.0",
   537  	)
   538  	_, ok := bootstrap.args.HostedModelConfig["authorized-keys"]
   539  	c.Assert(ok, jc.IsFalse)
   540  	_, ok = bootstrap.args.HostedModelConfig["agent-version"]
   541  	c.Assert(ok, jc.IsFalse)
   542  }
   543  
   544  func (s *BootstrapSuite) TestBootstrapWithGUI(c *gc.C) {
   545  	s.patchVersionAndSeries(c, "raring")
   546  	var bootstrap fakeBootstrapFuncs
   547  
   548  	s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface {
   549  		return &bootstrap
   550  	})
   551  	coretesting.RunCommand(c, s.newBootstrapCommandWrapper(false), "devcontroller", "dummy")
   552  	c.Assert(bootstrap.args.GUIDataSourceBaseURL, gc.Equals, gui.DefaultBaseURL)
   553  }
   554  
   555  func (s *BootstrapSuite) TestBootstrapWithCustomizedGUI(c *gc.C) {
   556  	s.patchVersionAndSeries(c, "raring")
   557  	s.PatchEnvironment("JUJU_GUI_SIMPLESTREAMS_URL", "https://1.2.3.4/gui/streams")
   558  
   559  	var bootstrap fakeBootstrapFuncs
   560  	s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface {
   561  		return &bootstrap
   562  	})
   563  
   564  	coretesting.RunCommand(c, s.newBootstrapCommandWrapper(false), "devcontroller", "dummy")
   565  	c.Assert(bootstrap.args.GUIDataSourceBaseURL, gc.Equals, "https://1.2.3.4/gui/streams")
   566  }
   567  
   568  func (s *BootstrapSuite) TestBootstrapWithoutGUI(c *gc.C) {
   569  	s.patchVersionAndSeries(c, "raring")
   570  	var bootstrap fakeBootstrapFuncs
   571  
   572  	s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface {
   573  		return &bootstrap
   574  	})
   575  	coretesting.RunCommand(c, s.newBootstrapCommandWrapper(false), "devcontroller", "dummy", "--no-gui")
   576  	c.Assert(bootstrap.args.GUIDataSourceBaseURL, gc.Equals, "")
   577  }
   578  
   579  type mockBootstrapInstance struct {
   580  	instance.Instance
   581  }
   582  
   583  func (*mockBootstrapInstance) Addresses() ([]network.Address, error) {
   584  	return []network.Address{{Value: "localhost"}}, nil
   585  }
   586  
   587  // In the case where we cannot examine the client store, we want the
   588  // error to propagate back up to the user.
   589  func (s *BootstrapSuite) TestBootstrapPropagatesStoreErrors(c *gc.C) {
   590  	const controllerName = "devcontroller"
   591  	s.patchVersionAndSeries(c, "raring")
   592  
   593  	store := jujuclienttesting.NewStubStore()
   594  	store.SetErrors(errors.New("oh noes"))
   595  	cmd := &bootstrapCommand{}
   596  	cmd.SetClientStore(store)
   597  	wrapped := modelcmd.Wrap(cmd, modelcmd.WrapSkipModelFlags, modelcmd.WrapSkipDefaultModel)
   598  	_, err := coretesting.RunCommand(c, wrapped, controllerName, "dummy", "--auto-upgrade")
   599  	store.CheckCallNames(c, "CredentialForCloud")
   600  	c.Assert(err, gc.ErrorMatches, `loading credentials: oh noes`)
   601  }
   602  
   603  // When attempting to bootstrap, check that when prepare errors out,
   604  // bootstrap will stop immediately. Nothing will be destroyed.
   605  func (s *BootstrapSuite) TestBootstrapFailToPrepareDiesGracefully(c *gc.C) {
   606  	destroyed := false
   607  	s.PatchValue(&environsDestroy, func(name string, _ environs.Environ, _ jujuclient.ControllerStore) error {
   608  		c.Assert(name, gc.Equals, "decontroller")
   609  		destroyed = true
   610  		return nil
   611  	})
   612  
   613  	s.PatchValue(&bootstrapPrepare, func(
   614  		environs.BootstrapContext,
   615  		jujuclient.ClientStore,
   616  		bootstrap.PrepareParams,
   617  	) (environs.Environ, error) {
   618  		return nil, errors.New("mock-prepare")
   619  	})
   620  
   621  	ctx := coretesting.Context(c)
   622  	_, errc := cmdtesting.RunCommand(
   623  		ctx, s.newBootstrapCommand(),
   624  		"devcontroller", "dummy",
   625  	)
   626  	c.Check(<-errc, gc.ErrorMatches, ".*mock-prepare$")
   627  	c.Check(destroyed, jc.IsFalse)
   628  }
   629  
   630  type controllerModelAccountParams struct {
   631  	controller     string
   632  	controllerUUID string
   633  	model          string
   634  	user           string
   635  }
   636  
   637  func (s *BootstrapSuite) writeControllerModelAccountInfo(c *gc.C, context *controllerModelAccountParams) {
   638  	controller := context.controller
   639  	model := context.model
   640  	user := context.user
   641  	controllerUUID := "a-uuid"
   642  	if context.controllerUUID != "" {
   643  		controllerUUID = context.controllerUUID
   644  	}
   645  	err := s.store.AddController(controller, jujuclient.ControllerDetails{
   646  		CACert:         "a-cert",
   647  		ControllerUUID: controllerUUID,
   648  	})
   649  	c.Assert(err, jc.ErrorIsNil)
   650  	err = s.store.SetCurrentController(controller)
   651  	c.Assert(err, jc.ErrorIsNil)
   652  	err = s.store.UpdateAccount(controller, jujuclient.AccountDetails{
   653  		User:     user,
   654  		Password: "secret",
   655  	})
   656  	c.Assert(err, jc.ErrorIsNil)
   657  	err = s.store.UpdateModel(controller, model, jujuclient.ModelDetails{
   658  		ModelUUID: "model-uuid",
   659  	})
   660  	c.Assert(err, jc.ErrorIsNil)
   661  	err = s.store.SetCurrentModel(controller, model)
   662  	c.Assert(err, jc.ErrorIsNil)
   663  }
   664  
   665  func (s *BootstrapSuite) TestBootstrapErrorRestoresOldMetadata(c *gc.C) {
   666  	s.patchVersionAndSeries(c, "raring")
   667  	s.PatchValue(&bootstrapPrepare, func(
   668  		environs.BootstrapContext,
   669  		jujuclient.ClientStore,
   670  		bootstrap.PrepareParams,
   671  	) (environs.Environ, error) {
   672  		ctx := controllerModelAccountParams{
   673  			controller: "foo",
   674  			model:      "foobar@local/bar",
   675  			user:       "foobar@local",
   676  		}
   677  		s.writeControllerModelAccountInfo(c, &ctx)
   678  		return nil, errors.New("mock-prepare")
   679  	})
   680  
   681  	ctx := controllerModelAccountParams{
   682  		controller:     "olddevcontroller",
   683  		controllerUUID: "another-uuid",
   684  		model:          "fred@local/fredmodel",
   685  		user:           "fred@local",
   686  	}
   687  	s.writeControllerModelAccountInfo(c, &ctx)
   688  	_, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "devcontroller", "dummy", "--auto-upgrade")
   689  	c.Assert(err, gc.ErrorMatches, "mock-prepare")
   690  
   691  	currentController := s.store.CurrentControllerName
   692  	c.Assert(currentController, gc.Equals, "olddevcontroller")
   693  	accountDetails, err := s.store.AccountDetails(currentController)
   694  	c.Assert(err, jc.ErrorIsNil)
   695  	c.Assert(accountDetails.User, gc.Equals, "fred@local")
   696  	currentModel, err := s.store.CurrentModel(currentController)
   697  	c.Assert(err, jc.ErrorIsNil)
   698  	c.Assert(currentModel, gc.Equals, "fred@local/fredmodel")
   699  }
   700  
   701  func (s *BootstrapSuite) TestBootstrapAlreadyExists(c *gc.C) {
   702  	const controllerName = "devcontroller"
   703  	s.patchVersionAndSeries(c, "raring")
   704  
   705  	cmaCtx := controllerModelAccountParams{
   706  		controller: "devcontroller",
   707  		model:      "fred@local/fredmodel",
   708  		user:       "fred@local",
   709  	}
   710  	s.writeControllerModelAccountInfo(c, &cmaCtx)
   711  
   712  	ctx := coretesting.Context(c)
   713  	_, errc := cmdtesting.RunCommand(ctx, s.newBootstrapCommand(), controllerName, "dummy", "--auto-upgrade")
   714  	err := <-errc
   715  	c.Assert(err, jc.Satisfies, errors.IsAlreadyExists)
   716  	c.Assert(err, gc.ErrorMatches, fmt.Sprintf(`controller %q already exists`, controllerName))
   717  	currentController := s.store.CurrentControllerName
   718  	c.Assert(currentController, gc.Equals, "devcontroller")
   719  	accountDetails, err := s.store.AccountDetails(currentController)
   720  	c.Assert(err, jc.ErrorIsNil)
   721  	c.Assert(accountDetails.User, gc.Equals, "fred@local")
   722  	currentModel, err := s.store.CurrentModel(currentController)
   723  	c.Assert(err, jc.ErrorIsNil)
   724  	c.Assert(currentModel, gc.Equals, "fred@local/fredmodel")
   725  }
   726  
   727  func (s *BootstrapSuite) TestInvalidLocalSource(c *gc.C) {
   728  	s.PatchValue(&jujuversion.Current, version.MustParse("1.2.0"))
   729  	resetJujuXDGDataHome(c)
   730  
   731  	// Bootstrap the controller with an invalid source.
   732  	// The command returns with an error.
   733  	_, err := coretesting.RunCommand(
   734  		c, s.newBootstrapCommand(), "--metadata-source", c.MkDir(),
   735  		"devcontroller", "dummy",
   736  	)
   737  	c.Check(err, gc.ErrorMatches, `failed to bootstrap model: Juju cannot bootstrap because no agent binaries are available for your model(.|\n)*`)
   738  }
   739  
   740  // createImageMetadata creates some image metadata in a local directory.
   741  func createImageMetadata(c *gc.C) (string, []*imagemetadata.ImageMetadata) {
   742  	// Generate some image metadata.
   743  	im := []*imagemetadata.ImageMetadata{
   744  		{
   745  			Id:         "1234",
   746  			Arch:       "amd64",
   747  			Version:    "13.04",
   748  			RegionName: "region",
   749  			Endpoint:   "endpoint",
   750  		},
   751  	}
   752  	cloudSpec := &simplestreams.CloudSpec{
   753  		Region:   "region",
   754  		Endpoint: "endpoint",
   755  	}
   756  	sourceDir := c.MkDir()
   757  	sourceStor, err := filestorage.NewFileStorageWriter(sourceDir)
   758  	c.Assert(err, jc.ErrorIsNil)
   759  	err = imagemetadata.MergeAndWriteMetadata("raring", im, cloudSpec, sourceStor)
   760  	c.Assert(err, jc.ErrorIsNil)
   761  	return sourceDir, im
   762  }
   763  
   764  func (s *BootstrapSuite) TestBootstrapCalledWithMetadataDir(c *gc.C) {
   765  	sourceDir, _ := createImageMetadata(c)
   766  	resetJujuXDGDataHome(c)
   767  
   768  	var bootstrap fakeBootstrapFuncs
   769  	s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface {
   770  		return &bootstrap
   771  	})
   772  
   773  	coretesting.RunCommand(
   774  		c, s.newBootstrapCommand(),
   775  		"--metadata-source", sourceDir, "--constraints", "mem=4G",
   776  		"devcontroller", "dummy-cloud/region-1",
   777  		"--config", "default-series=raring",
   778  	)
   779  	c.Assert(bootstrap.args.MetadataDir, gc.Equals, sourceDir)
   780  }
   781  
   782  func (s *BootstrapSuite) checkBootstrapWithVersion(c *gc.C, vers, expect string) {
   783  	resetJujuXDGDataHome(c)
   784  
   785  	var bootstrap fakeBootstrapFuncs
   786  	s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface {
   787  		return &bootstrap
   788  	})
   789  
   790  	num := jujuversion.Current
   791  	num.Major = 2
   792  	num.Minor = 3
   793  	s.PatchValue(&jujuversion.Current, num)
   794  	coretesting.RunCommand(
   795  		c, s.newBootstrapCommand(),
   796  		"--agent-version", vers,
   797  		"devcontroller", "dummy-cloud/region-1",
   798  		"--config", "default-series=raring",
   799  	)
   800  	c.Assert(bootstrap.args.AgentVersion, gc.NotNil)
   801  	c.Assert(*bootstrap.args.AgentVersion, gc.Equals, version.MustParse(expect))
   802  }
   803  
   804  func (s *BootstrapSuite) TestBootstrapWithVersionNumber(c *gc.C) {
   805  	s.checkBootstrapWithVersion(c, "2.3.4", "2.3.4")
   806  }
   807  
   808  func (s *BootstrapSuite) TestBootstrapWithBinaryVersionNumber(c *gc.C) {
   809  	s.checkBootstrapWithVersion(c, "2.3.4-trusty-ppc64", "2.3.4")
   810  }
   811  
   812  func (s *BootstrapSuite) TestBootstrapWithAutoUpgrade(c *gc.C) {
   813  	resetJujuXDGDataHome(c)
   814  
   815  	var bootstrap fakeBootstrapFuncs
   816  	s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface {
   817  		return &bootstrap
   818  	})
   819  	coretesting.RunCommand(
   820  		c, s.newBootstrapCommand(),
   821  		"--auto-upgrade",
   822  		"devcontroller", "dummy-cloud/region-1",
   823  	)
   824  	c.Assert(bootstrap.args.AgentVersion, gc.IsNil)
   825  }
   826  
   827  func (s *BootstrapSuite) TestAutoSyncLocalSource(c *gc.C) {
   828  	sourceDir := createToolsSource(c, vAll)
   829  	s.PatchValue(&jujuversion.Current, version.MustParse("1.2.0"))
   830  	series.SetLatestLtsForTesting("trusty")
   831  	resetJujuXDGDataHome(c)
   832  
   833  	// Bootstrap the controller with the valid source.
   834  	// The bootstrapping has to show no error, because the tools
   835  	// are automatically synchronized.
   836  	_, err := coretesting.RunCommand(
   837  		c, s.newBootstrapCommand(), "--metadata-source", sourceDir,
   838  		"devcontroller", "dummy-cloud/region-1", "--config", "default-series=trusty",
   839  	)
   840  	c.Assert(err, jc.ErrorIsNil)
   841  
   842  	bootstrapConfig, params, err := modelcmd.NewGetBootstrapConfigParamsFunc(
   843  		coretesting.Context(c), s.store,
   844  	)("devcontroller")
   845  	c.Assert(err, jc.ErrorIsNil)
   846  	provider, err := environs.Provider(bootstrapConfig.CloudType)
   847  	c.Assert(err, jc.ErrorIsNil)
   848  	cfg, err := provider.PrepareConfig(*params)
   849  	c.Assert(err, jc.ErrorIsNil)
   850  
   851  	env, err := environs.New(environs.OpenParams{
   852  		Cloud:  params.Cloud,
   853  		Config: cfg,
   854  	})
   855  	c.Assert(err, jc.ErrorIsNil)
   856  	err = env.PrepareForBootstrap(envtesting.BootstrapContext(c))
   857  	c.Assert(err, jc.ErrorIsNil)
   858  
   859  	// Now check the available tools which are the 1.2.0 envtools.
   860  	checkTools(c, env, v120All)
   861  }
   862  
   863  func (s *BootstrapSuite) TestInteractiveBootstrap(c *gc.C) {
   864  	s.patchVersionAndSeries(c, "raring")
   865  
   866  	cmd := s.newBootstrapCommand()
   867  	err := coretesting.InitCommand(cmd, nil)
   868  	c.Assert(err, jc.ErrorIsNil)
   869  	ctx := coretesting.Context(c)
   870  	out := bytes.Buffer{}
   871  	ctx.Stdin = strings.NewReader(`
   872  dummy-cloud
   873  region-1
   874  my-dummy-cloud
   875  `[1:])
   876  	ctx.Stdout = &out
   877  	err = cmd.Run(ctx)
   878  	if err != nil {
   879  		c.Logf(out.String())
   880  	}
   881  	c.Assert(err, jc.ErrorIsNil)
   882  
   883  	name := s.store.CurrentControllerName
   884  	c.Assert(name, gc.Equals, "my-dummy-cloud")
   885  	controller := s.store.Controllers[name]
   886  	c.Assert(controller.Cloud, gc.Equals, "dummy-cloud")
   887  	c.Assert(controller.CloudRegion, gc.Equals, "region-1")
   888  }
   889  
   890  func (s *BootstrapSuite) setupAutoUploadTest(c *gc.C, vers, ser string) {
   891  	patchedVersion := version.MustParse(vers)
   892  	patchedVersion.Build = 1
   893  	s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c, &patchedVersion))
   894  	sourceDir := createToolsSource(c, vAll)
   895  	s.PatchValue(&envtools.DefaultBaseURL, sourceDir)
   896  
   897  	// Change the tools location to be the test location and also
   898  	// the version and ensure their later restoring.
   899  	// Set the current version to be something for which there are no tools
   900  	// so we can test that an upload is forced.
   901  	s.PatchValue(&jujuversion.Current, version.MustParse(vers))
   902  	s.PatchValue(&series.HostSeries, func() string { return ser })
   903  
   904  	// Create home with dummy provider and remove all
   905  	// of its envtools.
   906  	resetJujuXDGDataHome(c)
   907  }
   908  
   909  func (s *BootstrapSuite) TestAutoUploadAfterFailedSync(c *gc.C) {
   910  	s.PatchValue(&series.HostSeries, func() string { return series.LatestLts() })
   911  	s.setupAutoUploadTest(c, "1.7.3", "quantal")
   912  	// Run command and check for that upload has been run for tools matching
   913  	// the current juju version.
   914  	opc, errc := cmdtesting.RunCommand(
   915  		cmdtesting.NullContext(c), s.newBootstrapCommand(),
   916  		"devcontroller", "dummy-cloud/region-1",
   917  		"--config", "default-series=raring",
   918  		"--auto-upgrade",
   919  	)
   920  	select {
   921  	case err := <-errc:
   922  		c.Assert(err, jc.ErrorIsNil)
   923  	case <-time.After(coretesting.LongWait):
   924  		c.Fatal("timed out")
   925  	}
   926  	c.Check((<-opc).(dummy.OpBootstrap).Env, gc.Equals, bootstrap.ControllerModelName)
   927  	icfg := (<-opc).(dummy.OpFinalizeBootstrap).InstanceConfig
   928  	c.Assert(icfg, gc.NotNil)
   929  	c.Assert(icfg.AgentVersion().String(), gc.Equals, "1.7.3.1-raring-"+arch.HostArch())
   930  }
   931  
   932  func (s *BootstrapSuite) TestMissingToolsError(c *gc.C) {
   933  	s.setupAutoUploadTest(c, "1.8.3", "precise")
   934  
   935  	_, err := coretesting.RunCommand(c, s.newBootstrapCommand(),
   936  		"devcontroller", "dummy-cloud/region-1",
   937  		"--config", "default-series=raring", "--agent-version=1.8.4",
   938  	)
   939  	c.Assert(err, gc.ErrorMatches,
   940  		"failed to bootstrap model: Juju cannot bootstrap because no agent binaries are available for your model(.|\n)*")
   941  }
   942  
   943  func (s *BootstrapSuite) TestMissingToolsUploadFailedError(c *gc.C) {
   944  
   945  	BuildAgentTarballAlwaysFails := func(build bool, forceVersion *version.Number, stream string) (*sync.BuiltAgent, error) {
   946  		return nil, errors.New("an error")
   947  	}
   948  
   949  	s.setupAutoUploadTest(c, "1.7.3", "precise")
   950  	s.PatchValue(&sync.BuildAgentTarball, BuildAgentTarballAlwaysFails)
   951  
   952  	ctx, err := coretesting.RunCommand(
   953  		c, s.newBootstrapCommand(),
   954  		"devcontroller", "dummy-cloud/region-1",
   955  		"--config", "default-series=raring",
   956  		"--config", "agent-stream=proposed",
   957  		"--auto-upgrade", "--agent-version=1.7.3",
   958  	)
   959  
   960  	c.Check(coretesting.Stderr(ctx), gc.Equals, `
   961  Creating Juju controller "devcontroller" on dummy-cloud/region-1
   962  Looking for packaged Juju agent version 1.7.3 for amd64
   963  No packaged binary found, preparing local Juju agent binary
   964  `[1:])
   965  	c.Check(err, gc.ErrorMatches, "failed to bootstrap model: cannot package bootstrap agent binary: an error")
   966  }
   967  
   968  func (s *BootstrapSuite) TestBootstrapDestroy(c *gc.C) {
   969  	resetJujuXDGDataHome(c)
   970  	s.patchVersion(c)
   971  
   972  	opc, errc := cmdtesting.RunCommand(
   973  		cmdtesting.NullContext(c), s.newBootstrapCommand(),
   974  		"devcontroller", "dummy-cloud/region-1",
   975  		"--config", "broken=Bootstrap Destroy",
   976  		"--auto-upgrade",
   977  	)
   978  	select {
   979  	case err := <-errc:
   980  		c.Assert(err, gc.ErrorMatches, "failed to bootstrap model: dummy.Bootstrap is broken")
   981  	case <-time.After(coretesting.LongWait):
   982  		c.Fatal("timed out")
   983  	}
   984  
   985  	var opDestroy *dummy.OpDestroy
   986  	for opDestroy == nil {
   987  		select {
   988  		case op := <-opc:
   989  			switch op := op.(type) {
   990  			case dummy.OpDestroy:
   991  				opDestroy = &op
   992  			}
   993  		default:
   994  			c.Error("expected call to env.Destroy")
   995  			return
   996  		}
   997  	}
   998  	c.Assert(opDestroy.Error, gc.ErrorMatches, "dummy.Destroy is broken")
   999  }
  1000  
  1001  func (s *BootstrapSuite) TestBootstrapKeepBroken(c *gc.C) {
  1002  	resetJujuXDGDataHome(c)
  1003  	s.patchVersion(c)
  1004  
  1005  	ctx := coretesting.Context(c)
  1006  	opc, errc := cmdtesting.RunCommand(ctx, s.newBootstrapCommand(),
  1007  		"--keep-broken",
  1008  		"devcontroller", "dummy-cloud/region-1",
  1009  		"--config", "broken=Bootstrap Destroy",
  1010  		"--auto-upgrade",
  1011  	)
  1012  	select {
  1013  	case err := <-errc:
  1014  		c.Assert(err, gc.ErrorMatches, "failed to bootstrap model: dummy.Bootstrap is broken")
  1015  	case <-time.After(coretesting.LongWait):
  1016  		c.Fatal("timed out")
  1017  	}
  1018  	done := false
  1019  	for !done {
  1020  		select {
  1021  		case op, ok := <-opc:
  1022  			if !ok {
  1023  				done = true
  1024  				break
  1025  			}
  1026  			switch op.(type) {
  1027  			case dummy.OpDestroy:
  1028  				c.Error("unexpected call to env.Destroy")
  1029  				break
  1030  			}
  1031  		default:
  1032  			break
  1033  		}
  1034  	}
  1035  	stderr := strings.Replace(coretesting.Stderr(ctx), "\n", " ", -1)
  1036  	c.Assert(stderr, gc.Matches, `.*See .*juju kill\-controller.*`)
  1037  }
  1038  
  1039  func (s *BootstrapSuite) TestBootstrapUnknownCloudOrProvider(c *gc.C) {
  1040  	s.patchVersionAndSeries(c, "raring")
  1041  	_, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "no-such-provider")
  1042  	c.Assert(err, gc.ErrorMatches, `unknown cloud "no-such-provider", please try "juju update-clouds"`)
  1043  }
  1044  
  1045  func (s *BootstrapSuite) TestBootstrapProviderNoRegionDetection(c *gc.C) {
  1046  	s.patchVersionAndSeries(c, "raring")
  1047  	_, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "no-cloud-region-detection")
  1048  	c.Assert(err, gc.ErrorMatches, `unknown cloud "no-cloud-region-detection", please try "juju update-clouds"`)
  1049  }
  1050  
  1051  func (s *BootstrapSuite) TestBootstrapProviderNoRegions(c *gc.C) {
  1052  	ctx, err := coretesting.RunCommand(
  1053  		c, s.newBootstrapCommand(), "ctrl", "no-cloud-regions",
  1054  		"--config", "default-series=precise",
  1055  	)
  1056  	c.Check(coretesting.Stderr(ctx), gc.Matches, "Creating Juju controller \"ctrl\" on no-cloud-regions(.|\n)*")
  1057  	c.Assert(err, jc.ErrorIsNil)
  1058  }
  1059  
  1060  func (s *BootstrapSuite) TestBootstrapCloudNoRegions(c *gc.C) {
  1061  	resetJujuXDGDataHome(c)
  1062  	ctx, err := coretesting.RunCommand(
  1063  		c, s.newBootstrapCommand(), "ctrl", "dummy-cloud-without-regions",
  1064  		"--config", "default-series=precise",
  1065  	)
  1066  	c.Check(coretesting.Stderr(ctx), gc.Matches, "Creating Juju controller \"ctrl\" on dummy-cloud-without-regions(.|\n)*")
  1067  	c.Assert(err, jc.ErrorIsNil)
  1068  }
  1069  
  1070  func (s *BootstrapSuite) TestBootstrapCloudNoRegionsOneSpecified(c *gc.C) {
  1071  	resetJujuXDGDataHome(c)
  1072  	ctx, err := coretesting.RunCommand(
  1073  		c, s.newBootstrapCommand(), "ctrl", "dummy-cloud-without-regions/my-region",
  1074  		"--config", "default-series=precise",
  1075  	)
  1076  	c.Check(coretesting.Stderr(ctx), gc.Matches,
  1077  		"region \"my-region\" not found \\(expected one of \\[\\]\\)\n\n.*")
  1078  	c.Assert(err, gc.Equals, cmd.ErrSilent)
  1079  }
  1080  
  1081  func (s *BootstrapSuite) TestBootstrapProviderNoCredentials(c *gc.C) {
  1082  	s.patchVersionAndSeries(c, "raring")
  1083  	_, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "no-credentials")
  1084  	c.Assert(err, gc.ErrorMatches, `detecting credentials for "no-credentials" cloud provider: credentials not found`)
  1085  }
  1086  
  1087  func (s *BootstrapSuite) TestBootstrapProviderManyDetectedCredentials(c *gc.C) {
  1088  	s.patchVersionAndSeries(c, "raring")
  1089  	_, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "many-credentials")
  1090  	c.Assert(err, gc.ErrorMatches, ambiguousDetectedCredentialError.Error())
  1091  }
  1092  
  1093  func (s *BootstrapSuite) TestBootstrapProviderDetectRegionsInvalid(c *gc.C) {
  1094  	s.patchVersionAndSeries(c, "raring")
  1095  	ctx, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "dummy/not-dummy")
  1096  	c.Assert(err, gc.Equals, cmd.ErrSilent)
  1097  	stderr := strings.Replace(coretesting.Stderr(ctx), "\n", "", -1)
  1098  	c.Assert(stderr, gc.Matches, `region "not-dummy" not found \(expected one of \["dummy"\]\)Specify an alternative region, or try "juju update-clouds".`)
  1099  }
  1100  
  1101  func (s *BootstrapSuite) TestBootstrapProviderManyCredentialsCloudNoAuthTypes(c *gc.C) {
  1102  	var bootstrap fakeBootstrapFuncs
  1103  	s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface {
  1104  		return &bootstrap
  1105  	})
  1106  
  1107  	s.patchVersionAndSeries(c, "raring")
  1108  	s.store.Credentials = map[string]cloud.CloudCredential{
  1109  		"many-credentials-no-auth-types": {
  1110  			AuthCredentials: map[string]cloud.Credential{"one": cloud.NewCredential("one", nil)},
  1111  		},
  1112  	}
  1113  	coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl",
  1114  		"many-credentials-no-auth-types",
  1115  		"--credential", "one",
  1116  	)
  1117  	c.Assert(bootstrap.args.Cloud.AuthTypes, jc.SameContents, cloud.AuthTypes{"one", "two"})
  1118  }
  1119  
  1120  func (s *BootstrapSuite) TestManyAvailableCredentialsNoneSpecified(c *gc.C) {
  1121  	var bootstrap fakeBootstrapFuncs
  1122  	s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface {
  1123  		return &bootstrap
  1124  	})
  1125  
  1126  	s.patchVersionAndSeries(c, "raring")
  1127  	s.store.Credentials = map[string]cloud.CloudCredential{
  1128  		"dummy": {
  1129  			AuthCredentials: map[string]cloud.Credential{
  1130  				"one": cloud.NewCredential("one", nil),
  1131  				"two": cloud.NewCredential("two", nil),
  1132  			},
  1133  		},
  1134  	}
  1135  	_, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "dummy")
  1136  	msg := strings.Replace(err.Error(), "\n", "", -1)
  1137  	c.Assert(msg, gc.Matches, "more than one credential is available.*")
  1138  }
  1139  
  1140  func (s *BootstrapSuite) TestBootstrapProviderDetectRegions(c *gc.C) {
  1141  	resetJujuXDGDataHome(c)
  1142  
  1143  	var bootstrap fakeBootstrapFuncs
  1144  	bootstrap.cloudRegionDetector = cloudRegionDetectorFunc(func() ([]cloud.Region, error) {
  1145  		return []cloud.Region{{Name: "bruce", Endpoint: "endpoint"}}, nil
  1146  	})
  1147  	s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface {
  1148  		return &bootstrap
  1149  	})
  1150  
  1151  	s.patchVersionAndSeries(c, "raring")
  1152  	coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "dummy")
  1153  	c.Assert(bootstrap.args.CloudRegion, gc.Equals, "bruce")
  1154  	c.Assert(bootstrap.args.CloudCredentialName, gc.Equals, "default")
  1155  	sort.Sort(bootstrap.args.Cloud.AuthTypes)
  1156  	c.Assert(bootstrap.args.Cloud, jc.DeepEquals, cloud.Cloud{
  1157  		Type:      "dummy",
  1158  		AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType},
  1159  		Regions:   []cloud.Region{{Name: "bruce", Endpoint: "endpoint"}},
  1160  	})
  1161  }
  1162  
  1163  func (s *BootstrapSuite) TestBootstrapProviderDetectNoRegions(c *gc.C) {
  1164  	resetJujuXDGDataHome(c)
  1165  
  1166  	var bootstrap fakeBootstrapFuncs
  1167  	bootstrap.cloudRegionDetector = cloudRegionDetectorFunc(func() ([]cloud.Region, error) {
  1168  		return nil, errors.NotFoundf("regions")
  1169  	})
  1170  	s.PatchValue(&getBootstrapFuncs, func() BootstrapInterface {
  1171  		return &bootstrap
  1172  	})
  1173  
  1174  	s.patchVersionAndSeries(c, "raring")
  1175  	coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "dummy")
  1176  	c.Assert(bootstrap.args.CloudRegion, gc.Equals, "")
  1177  	sort.Sort(bootstrap.args.Cloud.AuthTypes)
  1178  	c.Assert(bootstrap.args.Cloud, jc.DeepEquals, cloud.Cloud{
  1179  		Type:      "dummy",
  1180  		AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType},
  1181  	})
  1182  }
  1183  
  1184  func (s *BootstrapSuite) TestBootstrapProviderCaseInsensitiveRegionCheck(c *gc.C) {
  1185  	s.patchVersionAndSeries(c, "raring")
  1186  
  1187  	var prepareParams bootstrap.PrepareParams
  1188  	s.PatchValue(&bootstrapPrepare, func(
  1189  		ctx environs.BootstrapContext,
  1190  		stor jujuclient.ClientStore,
  1191  		params bootstrap.PrepareParams,
  1192  	) (environs.Environ, error) {
  1193  		prepareParams = params
  1194  		return nil, errors.New("mock-prepare")
  1195  	})
  1196  
  1197  	_, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "ctrl", "dummy/DUMMY")
  1198  	c.Assert(err, gc.ErrorMatches, "mock-prepare")
  1199  	c.Assert(prepareParams.Cloud.Region, gc.Equals, "dummy")
  1200  }
  1201  
  1202  func (s *BootstrapSuite) TestBootstrapConfigFile(c *gc.C) {
  1203  	tmpdir := c.MkDir()
  1204  	configFile := filepath.Join(tmpdir, "config.yaml")
  1205  	err := ioutil.WriteFile(configFile, []byte("controller: not-a-bool\n"), 0644)
  1206  	c.Assert(err, jc.ErrorIsNil)
  1207  
  1208  	s.patchVersionAndSeries(c, "raring")
  1209  	_, err = coretesting.RunCommand(
  1210  		c, s.newBootstrapCommand(), "ctrl", "dummy",
  1211  		"--config", configFile,
  1212  	)
  1213  	c.Assert(err, gc.ErrorMatches, `invalid attribute value\(s\) for dummy cloud: controller: expected bool, got string.*`)
  1214  }
  1215  
  1216  func (s *BootstrapSuite) TestBootstrapMultipleConfigFiles(c *gc.C) {
  1217  	tmpdir := c.MkDir()
  1218  	configFile1 := filepath.Join(tmpdir, "config-1.yaml")
  1219  	err := ioutil.WriteFile(configFile1, []byte(
  1220  		"controller: not-a-bool\nbroken: Bootstrap\n",
  1221  	), 0644)
  1222  	c.Assert(err, jc.ErrorIsNil)
  1223  	configFile2 := filepath.Join(tmpdir, "config-2.yaml")
  1224  	err = ioutil.WriteFile(configFile2, []byte(
  1225  		"controller: false\n",
  1226  	), 0644)
  1227  
  1228  	s.patchVersionAndSeries(c, "raring")
  1229  	_, err = coretesting.RunCommand(
  1230  		c, s.newBootstrapCommand(), "ctrl", "dummy",
  1231  		"--auto-upgrade",
  1232  		// the second config file should replace attributes
  1233  		// with the same name from the first, but leave the
  1234  		// others alone.
  1235  		"--config", configFile1,
  1236  		"--config", configFile2,
  1237  	)
  1238  	c.Assert(err, gc.ErrorMatches, "failed to bootstrap model: dummy.Bootstrap is broken")
  1239  }
  1240  
  1241  func (s *BootstrapSuite) TestBootstrapConfigFileAndAdHoc(c *gc.C) {
  1242  	tmpdir := c.MkDir()
  1243  	configFile := filepath.Join(tmpdir, "config.yaml")
  1244  	err := ioutil.WriteFile(configFile, []byte("controller: not-a-bool\n"), 0644)
  1245  	c.Assert(err, jc.ErrorIsNil)
  1246  
  1247  	s.patchVersionAndSeries(c, "raring")
  1248  	_, err = coretesting.RunCommand(
  1249  		c, s.newBootstrapCommand(), "ctrl", "dummy",
  1250  		"--auto-upgrade",
  1251  		// Configuration specified on the command line overrides
  1252  		// anything specified in files, no matter what the order.
  1253  		"--config", "controller=false",
  1254  		"--config", configFile,
  1255  	)
  1256  	c.Assert(err, jc.ErrorIsNil)
  1257  }
  1258  
  1259  func (s *BootstrapSuite) TestBootstrapAutocertDNSNameBadPort(c *gc.C) {
  1260  	s.patchVersionAndSeries(c, "raring")
  1261  	_, err := coretesting.RunCommand(
  1262  		c, s.newBootstrapCommand(), "ctrl", "dummy",
  1263  		"--config", "autocert-dns-name=foo.example",
  1264  	)
  1265  	c.Assert(err, gc.ErrorMatches, `autocert-dns-name is set but it's not usually possible to obtain official certificates without api-port=443 config; use --force-api-port to override this if you plan on using a port forwarder`)
  1266  }
  1267  
  1268  func (s *BootstrapSuite) TestBootstrapAutocertDNSNameOKPort(c *gc.C) {
  1269  	s.patchVersionAndSeries(c, "raring")
  1270  	_, err := coretesting.RunCommand(
  1271  		c, s.newBootstrapCommand(), "ctrl", "dummy",
  1272  		"--config", "autocert-dns-name=foo.example",
  1273  		"--config", "api-port=443",
  1274  	)
  1275  	c.Assert(err, jc.ErrorIsNil)
  1276  }
  1277  
  1278  func (s *BootstrapSuite) TestBootstrapAutocertDNSNameForceAPIPort(c *gc.C) {
  1279  	s.patchVersionAndSeries(c, "raring")
  1280  	_, err := coretesting.RunCommand(
  1281  		c, s.newBootstrapCommand(), "ctrl", "dummy",
  1282  		"--config", "autocert-dns-name=foo.example",
  1283  		"--force-api-port",
  1284  	)
  1285  	c.Assert(err, jc.ErrorIsNil)
  1286  }
  1287  
  1288  func (s *BootstrapSuite) TestBootstrapCloudConfigAndAdHoc(c *gc.C) {
  1289  	s.patchVersionAndSeries(c, "raring")
  1290  	_, err := coretesting.RunCommand(
  1291  		c, s.newBootstrapCommand(), "ctrl", "dummy-cloud-with-config",
  1292  		"--auto-upgrade",
  1293  		// Configuration specified on the command line overrides
  1294  		// anything specified in files, no matter what the order.
  1295  		"--config", "controller=not-a-bool",
  1296  	)
  1297  	c.Assert(err, gc.ErrorMatches, `invalid attribute value\(s\) for dummy cloud: controller: expected bool, got .*`)
  1298  }
  1299  
  1300  func (s *BootstrapSuite) TestBootstrapPrintClouds(c *gc.C) {
  1301  	resetJujuXDGDataHome(c)
  1302  	s.store.Credentials = map[string]cloud.CloudCredential{
  1303  		"aws": {
  1304  			DefaultRegion: "us-west-1",
  1305  			AuthCredentials: map[string]cloud.Credential{
  1306  				"fred": {},
  1307  				"mary": {},
  1308  			},
  1309  		},
  1310  		"dummy-cloud": {
  1311  			DefaultRegion: "home",
  1312  			AuthCredentials: map[string]cloud.Credential{
  1313  				"joe": {},
  1314  			},
  1315  		},
  1316  	}
  1317  	defer func() {
  1318  		s.store = jujuclienttesting.NewMemStore()
  1319  	}()
  1320  
  1321  	ctx, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "--clouds")
  1322  	c.Assert(err, jc.ErrorIsNil)
  1323  	c.Assert(coretesting.Stdout(ctx), jc.DeepEquals, `
  1324  You can bootstrap on these clouds. See ‘--regions <cloud>’ for all regions.
  1325  Cloud                           Credentials  Default Region
  1326  aws                             fred         us-west-1
  1327                                  mary         
  1328  aws-china                                    
  1329  aws-gov                                      
  1330  azure                                        
  1331  azure-china                                  
  1332  cloudsigma                                   
  1333  google                                       
  1334  joyent                                       
  1335  rackspace                                    
  1336  localhost                                    
  1337  dummy-cloud                     joe          home
  1338  dummy-cloud-with-config                      
  1339  dummy-cloud-without-regions                  
  1340  many-credentials-no-auth-types               
  1341  
  1342  You will need to have a credential if you want to bootstrap on a cloud, see
  1343  ‘juju autoload-credentials’ and ‘juju add-credential’. The first credential
  1344  listed is the default. Add more clouds with ‘juju add-cloud’.
  1345  `[1:])
  1346  }
  1347  
  1348  func (s *BootstrapSuite) TestBootstrapPrintCloudRegions(c *gc.C) {
  1349  	resetJujuXDGDataHome(c)
  1350  	ctx, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "--regions", "aws")
  1351  	c.Assert(err, jc.ErrorIsNil)
  1352  	c.Assert(coretesting.Stdout(ctx), jc.DeepEquals, `
  1353  Showing regions for aws:
  1354  us-east-1
  1355  us-west-1
  1356  us-west-2
  1357  eu-west-1
  1358  eu-central-1
  1359  ap-southeast-1
  1360  ap-southeast-2
  1361  ap-northeast-1
  1362  ap-northeast-2
  1363  sa-east-1
  1364  `[1:])
  1365  }
  1366  
  1367  func (s *BootstrapSuite) TestBootstrapPrintCloudRegionsNoSuchCloud(c *gc.C) {
  1368  	resetJujuXDGDataHome(c)
  1369  	_, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "--regions", "foo")
  1370  	c.Assert(err, gc.ErrorMatches, "cloud foo not found")
  1371  }
  1372  
  1373  func (s *BootstrapSuite) TestBootstrapSetsControllerOnBase(c *gc.C) {
  1374  	// This test ensures that the controller name is correctly set on
  1375  	// on the bootstrap commands embedded ModelCommandBase. Without
  1376  	// this, the concurrent bootstraps fail.
  1377  	// See https://pad.lv/1604223
  1378  
  1379  	resetJujuXDGDataHome(c)
  1380  	s.patchVersionAndSeries(c, "raring")
  1381  
  1382  	const controllerName = "dev"
  1383  
  1384  	// Record the controller name seen by ModelCommandBase at the end of bootstrap.
  1385  	var seenControllerName string
  1386  	s.PatchValue(&waitForAgentInitialisation, func(_ *cmd.Context, base *modelcmd.ModelCommandBase, _, _ string) error {
  1387  		seenControllerName = base.ControllerName()
  1388  		return nil
  1389  	})
  1390  
  1391  	// Run the bootstrap command in another goroutine, sending the
  1392  	// dummy provider ops to opc.
  1393  	errc := make(chan error, 1)
  1394  	opc := make(chan dummy.Operation)
  1395  	dummy.Listen(opc)
  1396  	go func() {
  1397  		defer func() {
  1398  			dummy.Listen(nil)
  1399  			close(opc)
  1400  		}()
  1401  		com := s.newBootstrapCommand()
  1402  		args := []string{controllerName, "dummy", "--auto-upgrade"}
  1403  		if err := coretesting.InitCommand(com, args); err != nil {
  1404  			errc <- err
  1405  			return
  1406  		}
  1407  		errc <- com.Run(cmdtesting.NullContext(c))
  1408  	}()
  1409  
  1410  	// Wait for bootstrap to start.
  1411  	select {
  1412  	case op := <-opc:
  1413  		_, ok := op.(dummy.OpBootstrap)
  1414  		c.Assert(ok, jc.IsTrue)
  1415  	case <-time.After(coretesting.LongWait):
  1416  		c.Fatal("timed out")
  1417  	}
  1418  
  1419  	// Simulate another controller being bootstrapped during the
  1420  	// bootstrap. Changing the current controller shouldn't affect the
  1421  	// bootstrap process.
  1422  	c.Assert(s.store.AddController("another", jujuclient.ControllerDetails{
  1423  		ControllerUUID: "uuid",
  1424  		CACert:         "cert",
  1425  	}), jc.ErrorIsNil)
  1426  	c.Assert(s.store.SetCurrentController("another"), jc.ErrorIsNil)
  1427  
  1428  	// Let bootstrap finish.
  1429  	select {
  1430  	case op := <-opc:
  1431  		_, ok := op.(dummy.OpFinalizeBootstrap)
  1432  		c.Assert(ok, jc.IsTrue)
  1433  	case <-time.After(coretesting.LongWait):
  1434  		c.Fatal("timed out")
  1435  	}
  1436  
  1437  	// Ensure there were no errors reported.
  1438  	select {
  1439  	case err := <-errc:
  1440  		c.Assert(err, jc.ErrorIsNil)
  1441  	case <-time.After(coretesting.LongWait):
  1442  		c.Fatal("timed out")
  1443  	}
  1444  
  1445  	// Wait for the ops channel to close.
  1446  	select {
  1447  	case _, ok := <-opc:
  1448  		c.Assert(ok, jc.IsFalse)
  1449  	case <-time.After(coretesting.LongWait):
  1450  		c.Fatal("timed out")
  1451  	}
  1452  
  1453  	// Expect to see that the correct controller was in use at the end
  1454  	// of bootstrap.
  1455  	c.Assert(seenControllerName, gc.Equals, controllerName)
  1456  }
  1457  
  1458  // createToolsSource writes the mock tools and metadata into a temporary
  1459  // directory and returns it.
  1460  func createToolsSource(c *gc.C, versions []version.Binary) string {
  1461  	versionStrings := make([]string, len(versions))
  1462  	for i, vers := range versions {
  1463  		versionStrings[i] = vers.String()
  1464  	}
  1465  	source := c.MkDir()
  1466  	toolstesting.MakeTools(c, source, "released", versionStrings)
  1467  	return source
  1468  }
  1469  
  1470  // resetJujuXDGDataHome restores an new, clean Juju home environment without tools.
  1471  func resetJujuXDGDataHome(c *gc.C) {
  1472  	cloudsPath := cloud.JujuPersonalCloudsPath()
  1473  	err := ioutil.WriteFile(cloudsPath, []byte(`
  1474  clouds:
  1475      dummy-cloud:
  1476          type: dummy
  1477          regions:
  1478              region-1:
  1479              region-2:
  1480      dummy-cloud-without-regions:
  1481          type: dummy
  1482      dummy-cloud-with-config:
  1483          type: dummy
  1484          config:
  1485              broken: Bootstrap
  1486              controller: not-a-bool
  1487      many-credentials-no-auth-types:
  1488          type: many-credentials
  1489  `[1:]), 0644)
  1490  	c.Assert(err, jc.ErrorIsNil)
  1491  }
  1492  
  1493  // checkTools check if the environment contains the passed envtools.
  1494  func checkTools(c *gc.C, env environs.Environ, expected []version.Binary) {
  1495  	list, err := envtools.FindTools(
  1496  		env, jujuversion.Current.Major, jujuversion.Current.Minor, "released", coretools.Filter{})
  1497  	c.Check(err, jc.ErrorIsNil)
  1498  	c.Logf("found: " + list.String())
  1499  	urls := list.URLs()
  1500  	c.Check(urls, gc.HasLen, len(expected))
  1501  }
  1502  
  1503  var (
  1504  	v100d64 = version.MustParseBinary("1.0.0-raring-amd64")
  1505  	v100p64 = version.MustParseBinary("1.0.0-precise-amd64")
  1506  	v100q32 = version.MustParseBinary("1.0.0-quantal-i386")
  1507  	v100q64 = version.MustParseBinary("1.0.0-quantal-amd64")
  1508  	v120d64 = version.MustParseBinary("1.2.0-raring-amd64")
  1509  	v120p64 = version.MustParseBinary("1.2.0-precise-amd64")
  1510  	v120q32 = version.MustParseBinary("1.2.0-quantal-i386")
  1511  	v120q64 = version.MustParseBinary("1.2.0-quantal-amd64")
  1512  	v120t32 = version.MustParseBinary("1.2.0-trusty-i386")
  1513  	v120t64 = version.MustParseBinary("1.2.0-trusty-amd64")
  1514  	v190p32 = version.MustParseBinary("1.9.0-precise-i386")
  1515  	v190q64 = version.MustParseBinary("1.9.0-quantal-amd64")
  1516  	v200p64 = version.MustParseBinary("2.0.0-precise-amd64")
  1517  	v100All = []version.Binary{
  1518  		v100d64, v100p64, v100q64, v100q32,
  1519  	}
  1520  	v120All = []version.Binary{
  1521  		v120d64, v120p64, v120q64, v120q32, v120t32, v120t64,
  1522  	}
  1523  	v190All = []version.Binary{
  1524  		v190p32, v190q64,
  1525  	}
  1526  	v200All = []version.Binary{
  1527  		v200p64,
  1528  	}
  1529  	vAll = joinBinaryVersions(v100All, v120All, v190All, v200All)
  1530  )
  1531  
  1532  func joinBinaryVersions(versions ...[]version.Binary) []version.Binary {
  1533  	var all []version.Binary
  1534  	for _, versions := range versions {
  1535  		all = append(all, versions...)
  1536  	}
  1537  	return all
  1538  }
  1539  
  1540  // TODO(menn0): This fake BootstrapInterface implementation is
  1541  // currently quite minimal but could be easily extended to cover more
  1542  // test scenarios. This could help improve some of the tests in this
  1543  // file which execute large amounts of external functionality.
  1544  type fakeBootstrapFuncs struct {
  1545  	args                bootstrap.BootstrapParams
  1546  	cloudRegionDetector environs.CloudRegionDetector
  1547  }
  1548  
  1549  func (fake *fakeBootstrapFuncs) Bootstrap(ctx environs.BootstrapContext, env environs.Environ, args bootstrap.BootstrapParams) error {
  1550  	fake.args = args
  1551  	return nil
  1552  }
  1553  
  1554  func (fake *fakeBootstrapFuncs) CloudRegionDetector(environs.EnvironProvider) (environs.CloudRegionDetector, bool) {
  1555  	detector := fake.cloudRegionDetector
  1556  	if detector == nil {
  1557  		detector = cloudRegionDetectorFunc(func() ([]cloud.Region, error) {
  1558  			return nil, errors.NotFoundf("regions")
  1559  		})
  1560  	}
  1561  	return detector, true
  1562  }
  1563  
  1564  type noCloudRegionDetectionProvider struct {
  1565  	environs.EnvironProvider
  1566  }
  1567  
  1568  type noCloudRegionsProvider struct {
  1569  	environs.EnvironProvider
  1570  }
  1571  
  1572  func (noCloudRegionsProvider) DetectRegions() ([]cloud.Region, error) {
  1573  	return nil, errors.NotFoundf("regions")
  1574  }
  1575  
  1576  func (noCloudRegionsProvider) CredentialSchemas() map[cloud.AuthType]cloud.CredentialSchema {
  1577  	return nil
  1578  }
  1579  
  1580  type noCredentialsProvider struct {
  1581  	environs.EnvironProvider
  1582  }
  1583  
  1584  func (noCredentialsProvider) DetectRegions() ([]cloud.Region, error) {
  1585  	return []cloud.Region{{Name: "region"}}, nil
  1586  }
  1587  
  1588  func (noCredentialsProvider) DetectCredentials() (*cloud.CloudCredential, error) {
  1589  	return nil, errors.NotFoundf("credentials")
  1590  }
  1591  
  1592  func (noCredentialsProvider) CredentialSchemas() map[cloud.AuthType]cloud.CredentialSchema {
  1593  	return nil
  1594  }
  1595  
  1596  type manyCredentialsProvider struct {
  1597  	environs.EnvironProvider
  1598  }
  1599  
  1600  func (manyCredentialsProvider) DetectRegions() ([]cloud.Region, error) {
  1601  	return []cloud.Region{{Name: "region"}}, nil
  1602  }
  1603  
  1604  func (manyCredentialsProvider) DetectCredentials() (*cloud.CloudCredential, error) {
  1605  	return &cloud.CloudCredential{
  1606  		AuthCredentials: map[string]cloud.Credential{
  1607  			"one": cloud.NewCredential("one", nil),
  1608  			"two": {},
  1609  		},
  1610  	}, nil
  1611  }
  1612  
  1613  func (manyCredentialsProvider) CredentialSchemas() map[cloud.AuthType]cloud.CredentialSchema {
  1614  	return map[cloud.AuthType]cloud.CredentialSchema{"one": {}, "two": {}}
  1615  }
  1616  
  1617  type cloudRegionDetectorFunc func() ([]cloud.Region, error)
  1618  
  1619  func (c cloudRegionDetectorFunc) DetectRegions() ([]cloud.Region, error) {
  1620  	return c()
  1621  }