github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/environs/open.go (about)

     1  // Copyright 2011, 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package environs
     5  
     6  import (
     7  	"crypto/rand"
     8  	"fmt"
     9  	"io"
    10  	"time"
    11  
    12  	"github.com/juju/errors"
    13  
    14  	"github.com/juju/juju/cert"
    15  	"github.com/juju/juju/cloud"
    16  	"github.com/juju/juju/environs/config"
    17  	"github.com/juju/juju/jujuclient"
    18  )
    19  
    20  // ControllerModelName is the name of the admin model in each controller.
    21  const ControllerModelName = "admin"
    22  
    23  // adminUser is the initial admin user created for all controllers.
    24  const AdminUser = "admin@local"
    25  
    26  // New returns a new environment based on the provided configuration.
    27  func New(config *config.Config) (Environ, error) {
    28  	p, err := Provider(config.Type())
    29  	if err != nil {
    30  		return nil, errors.Trace(err)
    31  	}
    32  	return p.Open(config)
    33  }
    34  
    35  // PrepareParams contains the parameters for preparing a controller Environ
    36  // for bootstrapping.
    37  type PrepareParams struct {
    38  	// BaseConfig contains the base configuration for the controller.
    39  	//
    40  	// This includes the model name, cloud type, and any user-supplied
    41  	// configuration. It does not include any default attributes.
    42  	BaseConfig map[string]interface{}
    43  
    44  	// ControllerName is the name of the controller being prepared.
    45  	ControllerName string
    46  
    47  	// CloudName is the name of the cloud that the controller is being
    48  	// prepared for.
    49  	CloudName string
    50  
    51  	// CloudRegion is the name of the region of the cloud to create
    52  	// the Juju controller in. This will be empty for clouds without
    53  	// regions.
    54  	CloudRegion string
    55  
    56  	// CloudEndpoint is the location of the primary API endpoint to
    57  	// use when communicating with the cloud.
    58  	CloudEndpoint string
    59  
    60  	// CloudStorageEndpoint is the location of the API endpoint to use
    61  	// when communicating with the cloud's storage service. This will
    62  	// be empty for clouds that have no cloud-specific API endpoint.
    63  	CloudStorageEndpoint string
    64  
    65  	// Credential is the credential to use to bootstrap.
    66  	Credential cloud.Credential
    67  
    68  	// CredentialName is the name of the credential to use to bootstrap.
    69  	// This will be empty for auto-detected credentials.
    70  	CredentialName string
    71  }
    72  
    73  // Prepare prepares a new controller based on the provided configuration.
    74  // It is an error to prepare a controller if there already exists an
    75  // entry in the client store with the same name.
    76  //
    77  // Upon success, Prepare will update the ClientStore with the details of
    78  // the controller, admin account, and admin model.
    79  func Prepare(
    80  	ctx BootstrapContext,
    81  	store jujuclient.ClientStore,
    82  	args PrepareParams,
    83  ) (_ Environ, resultErr error) {
    84  
    85  	_, err := store.ControllerByName(args.ControllerName)
    86  	if err == nil {
    87  		return nil, errors.AlreadyExistsf("controller %q", args.ControllerName)
    88  	} else if !errors.IsNotFound(err) {
    89  		return nil, errors.Annotatef(err, "error reading controller %q info", args.ControllerName)
    90  	}
    91  
    92  	cloudType, ok := args.BaseConfig["type"].(string)
    93  	if !ok {
    94  		return nil, errors.NotFoundf("cloud type in base configuration")
    95  	}
    96  
    97  	p, err := Provider(cloudType)
    98  	if err != nil {
    99  		return nil, errors.Trace(err)
   100  	}
   101  
   102  	env, details, err := prepare(ctx, p, args)
   103  	if err != nil {
   104  		return nil, errors.Trace(err)
   105  	}
   106  	details.Cloud = args.CloudName
   107  	details.Credential = args.CredentialName
   108  
   109  	if err := decorateAndWriteInfo(
   110  		store, details, args.ControllerName, env.Config().Name(),
   111  	); err != nil {
   112  		return nil, errors.Annotatef(err, "cannot create controller %q info", args.ControllerName)
   113  	}
   114  	return env, nil
   115  }
   116  
   117  // decorateAndWriteInfo decorates the info struct with information
   118  // from the given cfg, and the writes that out to the filesystem.
   119  func decorateAndWriteInfo(
   120  	store jujuclient.ClientStore,
   121  	details prepareDetails,
   122  	controllerName, modelName string,
   123  ) error {
   124  	accountName := details.User
   125  	if err := store.UpdateController(controllerName, details.ControllerDetails); err != nil {
   126  		return errors.Trace(err)
   127  	}
   128  	if err := store.UpdateBootstrapConfig(controllerName, details.BootstrapConfig); err != nil {
   129  		return errors.Trace(err)
   130  	}
   131  	if err := store.UpdateAccount(controllerName, accountName, details.AccountDetails); err != nil {
   132  		return errors.Trace(err)
   133  	}
   134  	if err := store.SetCurrentAccount(controllerName, accountName); err != nil {
   135  		return errors.Trace(err)
   136  	}
   137  	if err := store.UpdateModel(controllerName, accountName, modelName, details.ModelDetails); err != nil {
   138  		return errors.Trace(err)
   139  	}
   140  	if err := store.SetCurrentModel(controllerName, accountName, modelName); err != nil {
   141  		return errors.Trace(err)
   142  	}
   143  	return nil
   144  }
   145  
   146  func prepare(
   147  	ctx BootstrapContext,
   148  	p EnvironProvider,
   149  	args PrepareParams,
   150  ) (Environ, prepareDetails, error) {
   151  	var details prepareDetails
   152  
   153  	cfg, err := config.New(config.UseDefaults, args.BaseConfig)
   154  	if err != nil {
   155  		return nil, details, errors.Trace(err)
   156  	}
   157  	cfg, adminSecret, err := ensureAdminSecret(cfg)
   158  	if err != nil {
   159  		return nil, details, errors.Annotate(err, "cannot generate admin-secret")
   160  	}
   161  	cfg, caCert, err := ensureCertificate(cfg)
   162  	if err != nil {
   163  		return nil, details, errors.Annotate(err, "cannot ensure CA certificate")
   164  	}
   165  
   166  	cfg, err = p.BootstrapConfig(BootstrapConfigParams{
   167  		cfg, args.Credential, args.CloudRegion,
   168  		args.CloudEndpoint, args.CloudStorageEndpoint,
   169  	})
   170  	if err != nil {
   171  		return nil, details, errors.Trace(err)
   172  	}
   173  	env, err := p.PrepareForBootstrap(ctx, cfg)
   174  	if err != nil {
   175  		return nil, details, errors.Trace(err)
   176  	}
   177  
   178  	// We store the base configuration only; we don't want the
   179  	// default attributes, generated secrets/certificates, or
   180  	// UUIDs stored in the bootstrap config. Make a copy, so
   181  	// we don't disturb the caller's config map.
   182  	details.Config = make(map[string]interface{})
   183  	for k, v := range args.BaseConfig {
   184  		details.Config[k] = v
   185  	}
   186  	delete(details.Config, config.ControllerUUIDKey)
   187  	delete(details.Config, config.UUIDKey)
   188  
   189  	details.CACert = caCert
   190  	details.ControllerUUID = cfg.ControllerUUID()
   191  	details.User = AdminUser
   192  	details.Password = adminSecret
   193  	details.ModelUUID = cfg.UUID()
   194  	details.CloudRegion = args.CloudRegion
   195  	details.CloudEndpoint = args.CloudEndpoint
   196  	details.CloudStorageEndpoint = args.CloudStorageEndpoint
   197  
   198  	return env, details, nil
   199  }
   200  
   201  type prepareDetails struct {
   202  	jujuclient.ControllerDetails
   203  	jujuclient.BootstrapConfig
   204  	jujuclient.AccountDetails
   205  	jujuclient.ModelDetails
   206  }
   207  
   208  // ensureAdminSecret returns a config with a non-empty admin-secret.
   209  func ensureAdminSecret(cfg *config.Config) (*config.Config, string, error) {
   210  	if secret := cfg.AdminSecret(); secret != "" {
   211  		return cfg, secret, nil
   212  	}
   213  
   214  	// Generate a random string.
   215  	buf := make([]byte, 16)
   216  	if _, err := io.ReadFull(rand.Reader, buf); err != nil {
   217  		return nil, "", errors.Annotate(err, "generating random secret")
   218  	}
   219  	secret := fmt.Sprintf("%x", buf)
   220  
   221  	cfg, err := cfg.Apply(map[string]interface{}{"admin-secret": secret})
   222  	if err != nil {
   223  		return nil, "", errors.Trace(err)
   224  	}
   225  	return cfg, secret, nil
   226  }
   227  
   228  // ensureCertificate generates a new CA certificate and
   229  // attaches it to the given controller configuration,
   230  // unless the configuration already has one.
   231  func ensureCertificate(cfg *config.Config) (*config.Config, string, error) {
   232  	caCert, hasCACert := cfg.CACert()
   233  	_, hasCAKey := cfg.CAPrivateKey()
   234  	if hasCACert && hasCAKey {
   235  		return cfg, caCert, nil
   236  	}
   237  	if hasCACert && !hasCAKey {
   238  		return nil, "", errors.Errorf("controller configuration with a certificate but no CA private key")
   239  	}
   240  
   241  	caCert, caKey, err := cert.NewCA(cfg.Name(), cfg.UUID(), time.Now().UTC().AddDate(10, 0, 0))
   242  	if err != nil {
   243  		return nil, "", errors.Trace(err)
   244  	}
   245  	cfg, err = cfg.Apply(map[string]interface{}{
   246  		config.CACertKey: string(caCert),
   247  		"ca-private-key": string(caKey),
   248  	})
   249  	if err != nil {
   250  		return nil, "", errors.Trace(err)
   251  	}
   252  	return cfg, string(caCert), nil
   253  }
   254  
   255  // Destroy destroys the controller and, if successful,
   256  // its associated configuration data from the given store.
   257  func Destroy(
   258  	controllerName string,
   259  	env Environ,
   260  	store jujuclient.ControllerRemover,
   261  ) error {
   262  	err := store.RemoveController(controllerName)
   263  	if err != nil && !errors.IsNotFound(err) {
   264  		return errors.Trace(err)
   265  	}
   266  	return errors.Trace(env.Destroy())
   267  }