github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/cloudconfig/instancecfg/instancecfg.go (about)

     1  // Copyright 2012, 2013, 2015, 2016 Canonical Ltd.
     2  // Copyright 2015 Cloudbase Solutions SRL
     3  // Licensed under the AGPLv3, see LICENCE file for details.
     4  
     5  package instancecfg
     6  
     7  import (
     8  	"encoding/json"
     9  	"fmt"
    10  	"net"
    11  	"os"
    12  	"path"
    13  	"reflect"
    14  	"strconv"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/juju/charm/v12"
    19  	"github.com/juju/errors"
    20  	"github.com/juju/loggo"
    21  	"github.com/juju/names/v5"
    22  	"github.com/juju/proxy"
    23  	"github.com/juju/utils/v3/shell"
    24  	"github.com/juju/version/v2"
    25  	"gopkg.in/yaml.v2"
    26  
    27  	"github.com/juju/juju/agent"
    28  	agenttools "github.com/juju/juju/agent/tools"
    29  	"github.com/juju/juju/api"
    30  	"github.com/juju/juju/cloud"
    31  	"github.com/juju/juju/controller"
    32  	corebase "github.com/juju/juju/core/base"
    33  	"github.com/juju/juju/core/constraints"
    34  	"github.com/juju/juju/core/instance"
    35  	"github.com/juju/juju/core/model"
    36  	"github.com/juju/juju/core/paths"
    37  	"github.com/juju/juju/environs/config"
    38  	"github.com/juju/juju/environs/imagemetadata"
    39  	"github.com/juju/juju/environs/tags"
    40  	"github.com/juju/juju/service"
    41  	"github.com/juju/juju/service/common"
    42  	"github.com/juju/juju/storage"
    43  	coretools "github.com/juju/juju/tools"
    44  )
    45  
    46  var logger = loggo.GetLogger("juju.cloudconfig.instancecfg")
    47  
    48  // InstanceConfig represents initialization information for a new juju instance.
    49  type InstanceConfig struct {
    50  	// Tags is a set of tags to set on the instance, if supported. This
    51  	// should be populated using the InstanceTags method in this package.
    52  	Tags map[string]string
    53  
    54  	// Bootstrap contains bootstrap-specific configuration. If this is set,
    55  	// Controller must also be set.
    56  	Bootstrap *BootstrapConfig
    57  
    58  	// Controller contains configuration for the controller
    59  	// used to manage this new instance.
    60  	ControllerConfig controller.Config
    61  
    62  	// The public key used to sign Juju simplestreams image metadata.
    63  	PublicImageSigningKey string
    64  
    65  	// APIInfo holds the means for the new instance to communicate with the
    66  	// juju state API. Unless the new instance is running a controller (Controller is
    67  	// set), there must be at least one controller address supplied.
    68  	// The entity name must match that of the instance being started,
    69  	// or be empty when starting a controller.
    70  	APIInfo *api.Info
    71  
    72  	// ControllerTag identifies the controller.
    73  	ControllerTag names.ControllerTag
    74  
    75  	// MachineNonce is set at provisioning/bootstrap time and used to
    76  	// ensure the agent is running on the correct instance.
    77  	MachineNonce string
    78  
    79  	// tools is the list of juju tools used to install the Juju agent
    80  	// on the new instance. Each of the entries in the list must have
    81  	// identical versions and hashes, but may have different URLs.
    82  	tools coretools.List
    83  
    84  	// TransientDataDir holds the directory that juju can use to write
    85  	// transient files that get purged after a system reboot.
    86  	TransientDataDir string
    87  
    88  	// DataDir holds the directory that juju state will be put in the new
    89  	// instance.
    90  	DataDir string
    91  
    92  	// LogDir holds the directory that juju logs will be written to.
    93  	LogDir string
    94  
    95  	// MetricsSpoolDir represents the spool directory path, where all
    96  	// metrics are stored.
    97  	MetricsSpoolDir string
    98  
    99  	// Jobs holds what machine jobs to run.
   100  	Jobs []model.MachineJob
   101  
   102  	// CloudInitOutputLog specifies the path to the output log for cloud-init.
   103  	// The directory containing the log file must already exist.
   104  	CloudInitOutputLog string
   105  
   106  	// CloudInitUserData defines key/value pairs from the model-config
   107  	// specified by the user.
   108  	CloudInitUserData map[string]interface{}
   109  
   110  	// MachineId identifies the new machine.
   111  	MachineId string
   112  
   113  	// MachineContainerType specifies the type of container that the instance
   114  	// is.  If the instance is not a container, then the type is "".
   115  	MachineContainerType instance.ContainerType
   116  
   117  	// MachineContainerHostname specifies the hostname to be used with the
   118  	// cloud config for the instance. If this is not set, hostname uses the default.
   119  	MachineContainerHostname string
   120  
   121  	// AuthorizedKeys specifies the keys that are allowed to
   122  	// connect to the instance (see cloudinit.SSHAddAuthorizedKeys)
   123  	// If no keys are supplied, there can be no ssh access to the node.
   124  	// On a bootstrap instance, that is fatal. On other
   125  	// instances it will mean that the ssh, scp and debug-hooks
   126  	// commands cannot work.
   127  	AuthorizedKeys string
   128  
   129  	// AgentEnvironment defines additional configuration variables to set in
   130  	// the instance agent config.
   131  	AgentEnvironment map[string]string
   132  
   133  	// DisableSSLHostnameVerification can be set to true to tell cloud-init
   134  	// that it shouldn't verify SSL certificates
   135  	DisableSSLHostnameVerification bool
   136  
   137  	// Base represents the instance base.
   138  	Base corebase.Base
   139  
   140  	// MachineAgentServiceName is the init service name for the Juju machine agent.
   141  	MachineAgentServiceName string
   142  
   143  	// LegacyProxySettings define normal http, https and ftp proxies.
   144  	// These values are written to the /etc for the user profile and systemd settings.
   145  	LegacyProxySettings proxy.Settings
   146  
   147  	// JujuProxySettings define normal http, https and ftp proxies for accessing
   148  	// the outside network. These values are not written to disk.
   149  	JujuProxySettings proxy.Settings
   150  
   151  	// AptProxySettings define the http, https and ftp proxy settings to use
   152  	// for apt, which may or may not be the same as the normal ProxySettings.
   153  	AptProxySettings proxy.Settings
   154  
   155  	// AptMirror defines an APT mirror location, which, if specified, will
   156  	// override the default APT sources.
   157  	AptMirror string
   158  
   159  	// SnapProxySettings define the http, https and ftp proxy settings to
   160  	// use for snap, which may or may not be the same as the normal
   161  	// ProxySettings.
   162  	SnapProxySettings proxy.Settings
   163  
   164  	// SnapStoreAssertions contains a list of assertions that must be
   165  	// passed to snapd together with a store proxy ID parameter before it
   166  	// can connect to a snap store proxy.
   167  	SnapStoreAssertions string
   168  
   169  	// SnapStoreProxyID references a store entry in the snap store
   170  	// assertion list that must be passed to snapd before it can connect to
   171  	// a snap store proxy.
   172  	SnapStoreProxyID string
   173  
   174  	// SnapStoreProxyURL specifies the address of the snap store proxy. If
   175  	// specified instead of the assertions/storeID settings above, juju can
   176  	// directly contact the proxy to retrieve the assertions and store ID.
   177  	SnapStoreProxyURL string
   178  
   179  	// The type of Simple Stream to download and deploy on this instance.
   180  	ImageStream string
   181  
   182  	// EnableOSRefreshUpdate specifies whether Juju will refresh its
   183  	// respective OS's updates list.
   184  	EnableOSRefreshUpdate bool
   185  
   186  	// EnableOSUpgrade defines Juju's behavior when provisioning
   187  	// instances. If enabled, the OS will perform any upgrades
   188  	// available as part of its provisioning.
   189  	EnableOSUpgrade bool
   190  
   191  	// NetBondReconfigureDelay defines the duration in seconds that the
   192  	// networking bridgescript should pause between ifdown, then
   193  	// ifup when bridging bonded interfaces. See bugs #1594855 and
   194  	// #1269921.
   195  	NetBondReconfigureDelay int
   196  
   197  	// Profiles is a slice of (lxd) profile names to be used by a container
   198  	Profiles []string
   199  }
   200  
   201  // BootstrapConfig represents bootstrap-specific initialization information
   202  // for a new juju instance. This is only relevant for the bootstrap machine.
   203  type BootstrapConfig struct {
   204  	StateInitializationParams
   205  
   206  	// ControllerCharm is a local controller charm to be used.
   207  	ControllerCharm string
   208  
   209  	// Timeout is the amount of time to wait for bootstrap to complete.
   210  	Timeout time.Duration
   211  
   212  	// InitialSSHHostKeys contains the initial SSH host keys to configure
   213  	// on the bootstrap machine, indexed by algorithm. These will only be
   214  	// valid for the initial SSH connection. The first thing we do upon
   215  	// making the initial SSH connection is to replace each of these host
   216  	// keys, to avoid the host keys being extracted from the metadata
   217  	// service by a bad actor post-bootstrap.
   218  	//
   219  	// Any existing host keys on the machine with algorithms not specified
   220  	// in the map will be left alone. This is important so that we do not
   221  	// trample on the host keys of manually provisioned machines.
   222  	InitialSSHHostKeys SSHHostKeys
   223  
   224  	// StateServingInfo holds the information for serving the state.
   225  	// This is only specified for bootstrap; controllers started
   226  	// subsequently will acquire their serving info from another
   227  	// server.
   228  	StateServingInfo controller.StateServingInfo
   229  
   230  	// JujuDbSnapPath is the path to a .snap file that will be used as the juju-db
   231  	// service.
   232  	JujuDbSnapPath string
   233  
   234  	// JujuDbSnapAssertions is a path to a .assert file that will be used
   235  	// to verify the .snap at JujuDbSnapPath
   236  	JujuDbSnapAssertionsPath string
   237  
   238  	// ControllerServiceType is the service type of a k8s controller.
   239  	ControllerServiceType string
   240  
   241  	// IgnoreProxy tells the boostrap provider to no deploy any controller
   242  	// proxying resources. Currently only used in k8s
   243  	IgnoreProxy bool
   244  
   245  	// ControllerExternalName is the external name of a k8s controller.
   246  	ControllerExternalName string
   247  
   248  	// ControllerExternalIPs is the list of external ips for a k8s controller.
   249  	ControllerExternalIPs []string
   250  }
   251  
   252  // SSHHostKeys contains the SSH host keys to configure for a bootstrap host.
   253  type SSHHostKeys []SSHKeyPair
   254  
   255  // SSHKeyPair is an SSH host key pair.
   256  type SSHKeyPair struct {
   257  	// Private contains the private key, PEM-encoded.
   258  	Private string
   259  
   260  	// Public contains the public key in authorized_keys format.
   261  	Public string
   262  
   263  	// PublicKeyAlgorithm contains the public key algorithm as defined by golang.org/x/crypto/ssh KeyAlgo*
   264  	PublicKeyAlgorithm string
   265  }
   266  
   267  // StateInitializationParams contains parameters for initializing the
   268  // state database.
   269  //
   270  // This structure will be passed to the bootstrap agent. To do so, the
   271  // Marshal and Unmarshal methods must be used.
   272  type StateInitializationParams struct {
   273  	// ControllerModelConfig holds the initial controller model configuration.
   274  	ControllerModelConfig *config.Config
   275  
   276  	// ControllerModelEnvironVersion holds the initial controller model
   277  	// environ version.
   278  	ControllerModelEnvironVersion int
   279  
   280  	// ControllerCloud contains the properties of the cloud that Juju will
   281  	// be bootstrapped in.
   282  	ControllerCloud cloud.Cloud
   283  
   284  	// ControllerCloudRegion is the name of the cloud region that Juju will be
   285  	// bootstrapped in.
   286  	ControllerCloudRegion string
   287  
   288  	// ControllerCloudCredentialName is the name of the cloud credential that
   289  	// Juju will be bootstrapped with.
   290  	ControllerCloudCredentialName string
   291  
   292  	// ControllerCloudCredential contains the cloud credential that Juju will
   293  	// be bootstrapped with.
   294  	ControllerCloudCredential *cloud.Credential
   295  
   296  	// ControllerConfig is the set of config attributes relevant
   297  	// to a controller.
   298  	ControllerConfig controller.Config
   299  
   300  	// ControllerCharmPath points to a controller charm on Charmhub.
   301  	ControllerCharmPath string
   302  
   303  	// ControllerCharmChannel is used when deploying the controller charm.
   304  	ControllerCharmChannel charm.Channel
   305  
   306  	// ControllerInheritedConfig is a set of config attributes to be shared by all
   307  	// models managed by this controller.
   308  	ControllerInheritedConfig map[string]interface{}
   309  
   310  	// RegionInheritedConfig holds region specific configuration attributes to
   311  	// be shared across all models in the same controller on a particular
   312  	// cloud.
   313  	RegionInheritedConfig cloud.RegionConfig
   314  
   315  	// InitialModelConfig is a set of config attributes to be overlaid
   316  	// on the controller model config (Config, above) to construct the
   317  	// initial hosted model config.
   318  	InitialModelConfig map[string]interface{}
   319  
   320  	// BootstrapMachineInstanceId is the instance ID of the bootstrap
   321  	// machine instance being initialized.
   322  	BootstrapMachineInstanceId instance.Id
   323  
   324  	// BootstrapMachineDisplayName is the human readable name for
   325  	// the bootstrap machine instance being initialized.
   326  	BootstrapMachineDisplayName string
   327  
   328  	// BootstrapMachineConstraints holds the constraints for the bootstrap
   329  	// machine.
   330  	BootstrapMachineConstraints constraints.Value
   331  
   332  	// BootstrapMachineHardwareCharacteristics contains the hardware
   333  	// characteristics of the bootstrap machine instance being initialized.
   334  	BootstrapMachineHardwareCharacteristics *instance.HardwareCharacteristics
   335  
   336  	// ModelConstraints holds the initial model constraints.
   337  	ModelConstraints constraints.Value
   338  
   339  	// CustomImageMetadata is optional custom simplestreams image metadata
   340  	// to store in environment storage at bootstrap time. This is ignored
   341  	// in non-bootstrap instances.
   342  	CustomImageMetadata []*imagemetadata.ImageMetadata
   343  
   344  	// StoragePools is one or more named storage pools to create
   345  	// in the controller model.
   346  	StoragePools map[string]storage.Attrs
   347  }
   348  
   349  type stateInitializationParamsInternal struct {
   350  	ControllerConfig                        map[string]interface{}            `yaml:"controller-config"`
   351  	ControllerModelConfig                   map[string]interface{}            `yaml:"controller-model-config"`
   352  	ControllerModelEnvironVersion           int                               `yaml:"controller-model-version"`
   353  	ControllerInheritedConfig               map[string]interface{}            `yaml:"controller-config-defaults,omitempty"`
   354  	RegionInheritedConfig                   cloud.RegionConfig                `yaml:"region-inherited-config,omitempty"`
   355  	InitialModelConfig                      map[string]interface{}            `yaml:"initial-model-config,omitempty"`
   356  	StoragePools                            map[string]storage.Attrs          `yaml:"storage-pools,omitempty"`
   357  	BootstrapMachineInstanceId              instance.Id                       `yaml:"bootstrap-machine-instance-id,omitempty"`
   358  	BootstrapMachineConstraints             constraints.Value                 `yaml:"bootstrap-machine-constraints"`
   359  	BootstrapMachineHardwareCharacteristics *instance.HardwareCharacteristics `yaml:"bootstrap-machine-hardware,omitempty"`
   360  	BootstrapMachineDisplayName             string                            `yaml:"bootstrap-machine-display-name,omitempty"`
   361  	ModelConstraints                        constraints.Value                 `yaml:"model-constraints"`
   362  	CustomImageMetadataJSON                 string                            `yaml:"custom-image-metadata,omitempty"`
   363  	ControllerCloud                         string                            `yaml:"controller-cloud"`
   364  	ControllerCloudRegion                   string                            `yaml:"controller-cloud-region"`
   365  	ControllerCloudCredentialName           string                            `yaml:"controller-cloud-credential-name,omitempty"`
   366  	ControllerCloudCredential               *cloud.Credential                 `yaml:"controller-cloud-credential,omitempty"`
   367  	ControllerCharmPath                     string                            `yaml:"controller-charm-path,omitempty"`
   368  	ControllerCharmChannel                  charm.Channel                     `yaml:"controller-charm-channel,omitempty"`
   369  }
   370  
   371  // Marshal marshals StateInitializationParams to an opaque byte array.
   372  func (p *StateInitializationParams) Marshal() ([]byte, error) {
   373  	customImageMetadataJSON, err := json.Marshal(p.CustomImageMetadata)
   374  	if err != nil {
   375  		return nil, errors.Annotate(err, "marshalling custom image metadata")
   376  	}
   377  	controllerCloud, err := cloud.MarshalCloud(p.ControllerCloud)
   378  	if err != nil {
   379  		return nil, errors.Annotate(err, "marshalling cloud definition")
   380  	}
   381  	internal := stateInitializationParamsInternal{
   382  		ControllerConfig:                        p.ControllerConfig,
   383  		ControllerModelConfig:                   p.ControllerModelConfig.AllAttrs(),
   384  		ControllerModelEnvironVersion:           p.ControllerModelEnvironVersion,
   385  		ControllerInheritedConfig:               p.ControllerInheritedConfig,
   386  		RegionInheritedConfig:                   p.RegionInheritedConfig,
   387  		InitialModelConfig:                      p.InitialModelConfig,
   388  		StoragePools:                            p.StoragePools,
   389  		BootstrapMachineInstanceId:              p.BootstrapMachineInstanceId,
   390  		BootstrapMachineConstraints:             p.BootstrapMachineConstraints,
   391  		BootstrapMachineHardwareCharacteristics: p.BootstrapMachineHardwareCharacteristics,
   392  		BootstrapMachineDisplayName:             p.BootstrapMachineDisplayName,
   393  		ModelConstraints:                        p.ModelConstraints,
   394  		CustomImageMetadataJSON:                 string(customImageMetadataJSON),
   395  		ControllerCloud:                         string(controllerCloud),
   396  		ControllerCloudRegion:                   p.ControllerCloudRegion,
   397  		ControllerCloudCredentialName:           p.ControllerCloudCredentialName,
   398  		ControllerCloudCredential:               p.ControllerCloudCredential,
   399  		ControllerCharmPath:                     p.ControllerCharmPath,
   400  		ControllerCharmChannel:                  p.ControllerCharmChannel,
   401  	}
   402  	return yaml.Marshal(&internal)
   403  }
   404  
   405  // Unmarshal unmarshals StateInitializationParams from a byte array that
   406  // was generated with StateInitializationParams.Marshal.
   407  func (p *StateInitializationParams) Unmarshal(data []byte) error {
   408  	var internal stateInitializationParamsInternal
   409  	if err := yaml.Unmarshal(data, &internal); err != nil {
   410  		return errors.Annotate(err, "unmarshalling state initialization params")
   411  	}
   412  	var imageMetadata []*imagemetadata.ImageMetadata
   413  	if err := json.Unmarshal([]byte(internal.CustomImageMetadataJSON), &imageMetadata); err != nil {
   414  		return errors.Trace(err)
   415  	}
   416  	cfg, err := config.New(config.NoDefaults, internal.ControllerModelConfig)
   417  	if err != nil {
   418  		return errors.Trace(err)
   419  	}
   420  	controllerCloud, err := cloud.UnmarshalCloud([]byte(internal.ControllerCloud))
   421  	if err != nil {
   422  		return errors.Trace(err)
   423  	}
   424  	*p = StateInitializationParams{
   425  		ControllerConfig:                        internal.ControllerConfig,
   426  		ControllerModelConfig:                   cfg,
   427  		ControllerModelEnvironVersion:           internal.ControllerModelEnvironVersion,
   428  		ControllerInheritedConfig:               internal.ControllerInheritedConfig,
   429  		RegionInheritedConfig:                   internal.RegionInheritedConfig,
   430  		InitialModelConfig:                      internal.InitialModelConfig,
   431  		StoragePools:                            internal.StoragePools,
   432  		BootstrapMachineInstanceId:              internal.BootstrapMachineInstanceId,
   433  		BootstrapMachineConstraints:             internal.BootstrapMachineConstraints,
   434  		BootstrapMachineHardwareCharacteristics: internal.BootstrapMachineHardwareCharacteristics,
   435  		BootstrapMachineDisplayName:             internal.BootstrapMachineDisplayName,
   436  		ModelConstraints:                        internal.ModelConstraints,
   437  		CustomImageMetadata:                     imageMetadata,
   438  		ControllerCloud:                         controllerCloud,
   439  		ControllerCloudRegion:                   internal.ControllerCloudRegion,
   440  		ControllerCloudCredentialName:           internal.ControllerCloudCredentialName,
   441  		ControllerCloudCredential:               internal.ControllerCloudCredential,
   442  		ControllerCharmPath:                     internal.ControllerCharmPath,
   443  		ControllerCharmChannel:                  internal.ControllerCharmChannel,
   444  	}
   445  	return nil
   446  }
   447  
   448  func (cfg *InstanceConfig) agentInfo() service.AgentInfo {
   449  	return service.NewMachineAgentInfo(
   450  		cfg.MachineId,
   451  		cfg.DataDir,
   452  		cfg.LogDir,
   453  	)
   454  }
   455  
   456  func (cfg *InstanceConfig) IsController() bool {
   457  	return model.AnyJobNeedsState(cfg.Jobs...)
   458  }
   459  
   460  func (cfg *InstanceConfig) ToolsDir(renderer shell.Renderer) string {
   461  	return cfg.agentInfo().ToolsDir(renderer)
   462  }
   463  
   464  func (cfg *InstanceConfig) InitService(renderer shell.Renderer) (service.Service, error) {
   465  	conf := service.AgentConf(cfg.agentInfo(), renderer)
   466  
   467  	name := cfg.MachineAgentServiceName
   468  	svc, err := newService(name, conf)
   469  	return svc, errors.Trace(err)
   470  }
   471  
   472  var newService = func(name string, conf common.Conf) (service.Service, error) {
   473  	return service.NewService(name, conf)
   474  }
   475  
   476  func (cfg *InstanceConfig) AgentConfig(
   477  	tag names.Tag,
   478  	toolsVersion version.Number,
   479  ) (agent.ConfigSetter, error) {
   480  	configParams := agent.AgentConfigParams{
   481  		Paths: agent.Paths{
   482  			DataDir:          cfg.DataDir,
   483  			TransientDataDir: cfg.TransientDataDir,
   484  			LogDir:           cfg.LogDir,
   485  			MetricsSpoolDir:  cfg.MetricsSpoolDir,
   486  		},
   487  		Jobs:              cfg.Jobs,
   488  		Tag:               tag,
   489  		UpgradedToVersion: toolsVersion,
   490  		Password:          cfg.APIInfo.Password,
   491  		Nonce:             cfg.MachineNonce,
   492  		APIAddresses:      cfg.APIHostAddrs(),
   493  		CACert:            cfg.APIInfo.CACert,
   494  		Values:            cfg.AgentEnvironment,
   495  		Controller:        cfg.ControllerTag,
   496  		Model:             cfg.APIInfo.ModelTag,
   497  	}
   498  	if cfg.ControllerConfig != nil {
   499  		configParams.AgentLogfileMaxBackups = cfg.ControllerConfig.AgentLogfileMaxBackups()
   500  		configParams.AgentLogfileMaxSizeMB = cfg.ControllerConfig.AgentLogfileMaxSizeMB()
   501  		configParams.QueryTracingEnabled = cfg.ControllerConfig.QueryTracingEnabled()
   502  		configParams.QueryTracingThreshold = cfg.ControllerConfig.QueryTracingThreshold()
   503  	}
   504  	if cfg.Bootstrap == nil {
   505  		return agent.NewAgentConfig(configParams)
   506  	}
   507  	return agent.NewStateMachineConfig(configParams, cfg.Bootstrap.StateServingInfo)
   508  }
   509  
   510  // JujuTools returns the directory where Juju tools are stored.
   511  func (cfg *InstanceConfig) JujuTools() string {
   512  	return agenttools.SharedToolsDir(cfg.DataDir, cfg.AgentVersion())
   513  }
   514  
   515  // SnapDir returns the directory where snaps should be uploaded to.
   516  func (cfg *InstanceConfig) SnapDir() string {
   517  	return path.Join(cfg.DataDir, "snap")
   518  }
   519  
   520  // CharmDir returns the directory where system charms should be uploaded to.
   521  func (cfg *InstanceConfig) CharmDir() string {
   522  	return path.Join(cfg.DataDir, "charms")
   523  }
   524  
   525  func (cfg *InstanceConfig) APIHostAddrs() []string {
   526  	var hosts []string
   527  	if cfg.Bootstrap != nil {
   528  		hosts = append(hosts, net.JoinHostPort(
   529  			"localhost", strconv.Itoa(cfg.Bootstrap.StateServingInfo.APIPort)),
   530  		)
   531  	}
   532  	if cfg.APIInfo != nil {
   533  		hosts = append(hosts, cfg.APIInfo.Addrs...)
   534  	}
   535  	return hosts
   536  }
   537  
   538  func (cfg *InstanceConfig) APIHosts() []string {
   539  	var hosts []string
   540  	if cfg.Bootstrap != nil {
   541  		hosts = append(hosts, "localhost")
   542  	}
   543  	if cfg.APIInfo != nil {
   544  		for _, addr := range cfg.APIInfo.Addrs {
   545  			host, _, err := net.SplitHostPort(addr)
   546  			if err != nil {
   547  				logger.Errorf("Can't split API address %q to host:port - %q", host, err)
   548  				continue
   549  			}
   550  			hosts = append(hosts, host)
   551  		}
   552  	}
   553  	return hosts
   554  }
   555  
   556  // AgentVersion returns the version of the Juju agent that will be configured
   557  // on the instance. The zero value will be returned if there are no tools set.
   558  func (cfg *InstanceConfig) AgentVersion() version.Binary {
   559  	if len(cfg.tools) == 0 {
   560  		return version.Binary{}
   561  	}
   562  	return cfg.tools[0].Version
   563  }
   564  
   565  // ToolsList returns the list of tools in the order in which they will
   566  // be tried.
   567  func (cfg *InstanceConfig) ToolsList() coretools.List {
   568  	if cfg.tools == nil {
   569  		return nil
   570  	}
   571  	return copyToolsList(cfg.tools)
   572  }
   573  
   574  // SetTools sets the tools that should be tried when provisioning this
   575  // instance. There must be at least one. Other than the URL, each item
   576  // must be the same.
   577  //
   578  // TODO(axw) 2016-04-19 lp:1572116
   579  // SetTools should verify that the tools have URLs, since they will
   580  // be needed for downloading on the instance. We can't do that until
   581  // all usage-sites are updated to pass through non-empty URLs.
   582  func (cfg *InstanceConfig) SetTools(toolsList coretools.List) error {
   583  	if len(toolsList) == 0 {
   584  		return errors.New("need at least 1 agent binary")
   585  	}
   586  	var tools *coretools.Tools
   587  	for _, listed := range toolsList {
   588  		if listed == nil {
   589  			return errors.New("nil entry in agent binaries list")
   590  		}
   591  		info := *listed
   592  		info.URL = ""
   593  		if tools == nil {
   594  			tools = &info
   595  			continue
   596  		}
   597  		if !reflect.DeepEqual(info, *tools) {
   598  			return errors.Errorf("agent binary info mismatch (%v, %v)", *tools, info)
   599  		}
   600  	}
   601  	cfg.tools = copyToolsList(toolsList)
   602  	return nil
   603  }
   604  
   605  func copyToolsList(in coretools.List) coretools.List {
   606  	out := make(coretools.List, len(in))
   607  	for i, tools := range in {
   608  		copied := *tools
   609  		out[i] = &copied
   610  	}
   611  	return out
   612  }
   613  
   614  // SetSnapSource annotates the instance configuration
   615  // with the location of a local .snap to upload during
   616  // the instance's provisioning.
   617  func (cfg *InstanceConfig) SetSnapSource(snapPath string, snapAssertionsPath string) error {
   618  	if snapPath == "" {
   619  		return nil
   620  	}
   621  
   622  	_, err := os.Stat(snapPath)
   623  	if err != nil {
   624  		return errors.Annotatef(err, "unable set local snap (at %s)", snapPath)
   625  	}
   626  
   627  	_, err = os.Stat(snapAssertionsPath)
   628  	if err != nil {
   629  		return errors.Annotatef(err, "unable set local snap .assert (at %s)", snapAssertionsPath)
   630  	}
   631  
   632  	cfg.Bootstrap.JujuDbSnapPath = snapPath
   633  	cfg.Bootstrap.JujuDbSnapAssertionsPath = snapAssertionsPath
   634  
   635  	return nil
   636  }
   637  
   638  // SetControllerCharm annotates the instance configuration
   639  // with the location of a local controller charm to upload during
   640  // the instance's provisioning.
   641  func (cfg *InstanceConfig) SetControllerCharm(controllerCharmPath string) error {
   642  	if controllerCharmPath == "" {
   643  		return nil
   644  	}
   645  
   646  	_, err := os.Stat(controllerCharmPath)
   647  	if err != nil {
   648  		return errors.Annotatef(err, "unable set local controller charm (at %s)", controllerCharmPath)
   649  	}
   650  
   651  	cfg.Bootstrap.ControllerCharm = controllerCharmPath
   652  
   653  	return nil
   654  }
   655  
   656  // VerifyConfig verifies that the InstanceConfig is valid.
   657  func (cfg *InstanceConfig) VerifyConfig() (err error) {
   658  	defer errors.DeferredAnnotatef(&err, "invalid machine configuration")
   659  	if !names.IsValidMachine(cfg.MachineId) {
   660  		return errors.New("invalid machine id")
   661  	}
   662  	if cfg.DataDir == "" {
   663  		return errors.New("missing var directory")
   664  	}
   665  	if cfg.LogDir == "" {
   666  		return errors.New("missing log directory")
   667  	}
   668  	if cfg.MetricsSpoolDir == "" {
   669  		return errors.New("missing metrics spool directory")
   670  	}
   671  	if len(cfg.Jobs) == 0 {
   672  		return errors.New("missing machine jobs")
   673  	}
   674  	if cfg.CloudInitOutputLog == "" {
   675  		return errors.New("missing cloud-init output log path")
   676  	}
   677  	if cfg.tools == nil {
   678  		// SetTools() has never been called successfully.
   679  		return errors.New("missing agent binaries")
   680  	}
   681  	// We don't need to check cfg.toolsURLs since SetTools() does.
   682  	if cfg.APIInfo == nil {
   683  		return errors.New("missing API info")
   684  	}
   685  	if cfg.APIInfo.ModelTag.Id() == "" {
   686  		return errors.New("missing model tag")
   687  	}
   688  	if len(cfg.APIInfo.CACert) == 0 {
   689  		return errors.New("missing API CA certificate")
   690  	}
   691  	if cfg.MachineAgentServiceName == "" {
   692  		return errors.New("missing machine agent service name")
   693  	}
   694  	if cfg.MachineNonce == "" {
   695  		return errors.New("missing machine nonce")
   696  	}
   697  	if cfg.Bootstrap != nil {
   698  		if err := cfg.verifyBootstrapConfig(); err != nil {
   699  			return errors.Trace(err)
   700  		}
   701  	} else {
   702  		if cfg.APIInfo.Tag != names.NewMachineTag(cfg.MachineId) {
   703  			return errors.New("API entity tag must match started machine")
   704  		}
   705  		if len(cfg.APIInfo.Addrs) == 0 {
   706  			return errors.New("missing API hosts")
   707  		}
   708  	}
   709  	return nil
   710  }
   711  
   712  func (cfg *InstanceConfig) verifyBootstrapConfig() (err error) {
   713  	defer errors.DeferredAnnotatef(&err, "invalid bootstrap configuration")
   714  	if cfg.ControllerConfig == nil {
   715  		return errors.New("bootstrap config supplied without controller config")
   716  	}
   717  	if err := cfg.Bootstrap.VerifyConfig(); err != nil {
   718  		return errors.Trace(err)
   719  	}
   720  	if cfg.APIInfo.Tag != nil {
   721  		return errors.New("entity tag must be nil when bootstrapping")
   722  	}
   723  	return nil
   724  }
   725  
   726  // VerifyConfig verifies that the BootstrapConfig is valid.
   727  func (cfg *BootstrapConfig) VerifyConfig() (err error) {
   728  	if cfg.ControllerModelConfig == nil {
   729  		return errors.New("missing model configuration")
   730  	}
   731  	if len(cfg.StateServingInfo.Cert) == 0 {
   732  		return errors.New("missing controller certificate")
   733  	}
   734  	if len(cfg.StateServingInfo.PrivateKey) == 0 {
   735  		return errors.New("missing controller private key")
   736  	}
   737  	if len(cfg.StateServingInfo.CAPrivateKey) == 0 {
   738  		return errors.New("missing ca cert private key")
   739  	}
   740  	if cfg.StateServingInfo.StatePort == 0 {
   741  		return errors.New("missing state port")
   742  	}
   743  	if cfg.StateServingInfo.APIPort == 0 {
   744  		return errors.New("missing API port")
   745  	}
   746  	if cfg.BootstrapMachineInstanceId == "" {
   747  		return errors.New("missing bootstrap machine instance ID")
   748  	}
   749  	return nil
   750  }
   751  
   752  // DefaultBridgeName is the network bridge device name used for LXC and KVM
   753  // containers
   754  const DefaultBridgeName = "br-eth0"
   755  
   756  // NewInstanceConfig sets up a basic machine configuration, for a
   757  // non-bootstrap node. You'll still need to supply more information,
   758  // but this takes care of the fixed entries and the ones that are
   759  // always needed.
   760  func NewInstanceConfig(
   761  	controllerTag names.ControllerTag,
   762  	machineID,
   763  	machineNonce,
   764  	imageStream string,
   765  	base corebase.Base,
   766  	apiInfo *api.Info,
   767  ) (*InstanceConfig, error) {
   768  	osType := paths.OSType(base.OS)
   769  	logDir := paths.LogDir(osType)
   770  	icfg := &InstanceConfig{
   771  		// Fixed entries.
   772  		DataDir:                 paths.DataDir(osType),
   773  		LogDir:                  path.Join(logDir, "juju"),
   774  		MetricsSpoolDir:         paths.MetricsSpoolDir(osType),
   775  		Jobs:                    []model.MachineJob{model.JobHostUnits},
   776  		CloudInitOutputLog:      path.Join(logDir, "cloud-init-output.log"),
   777  		TransientDataDir:        paths.TransientDataDir(osType),
   778  		MachineAgentServiceName: "jujud-" + names.NewMachineTag(machineID).String(),
   779  		Base:                    base,
   780  		Tags:                    map[string]string{},
   781  
   782  		// Parameter entries.
   783  		ControllerTag: controllerTag,
   784  		MachineId:     machineID,
   785  		MachineNonce:  machineNonce,
   786  		APIInfo:       apiInfo,
   787  		ImageStream:   imageStream,
   788  	}
   789  	return icfg, nil
   790  }
   791  
   792  // NewBootstrapInstanceConfig sets up a basic machine configuration for a
   793  // bootstrap node.  You'll still need to supply more information, but this
   794  // takes care of the fixed entries and the ones that are always needed.
   795  func NewBootstrapInstanceConfig(
   796  	config controller.Config,
   797  	cons, modelCons constraints.Value,
   798  	base corebase.Base, publicImageSigningKey string,
   799  	agentEnvironment map[string]string,
   800  ) (*InstanceConfig, error) {
   801  	// For a bootstrap instance, the caller must provide the state.Info
   802  	// and the api.Info. The machine id must *always* be "0".
   803  	icfg, err := NewInstanceConfig(names.NewControllerTag(config.ControllerUUID()), agent.BootstrapControllerId, agent.BootstrapNonce, "", base, nil)
   804  	if err != nil {
   805  		return nil, err
   806  	}
   807  	icfg.PublicImageSigningKey = publicImageSigningKey
   808  	icfg.ControllerConfig = make(map[string]interface{})
   809  	for k, v := range config {
   810  		icfg.ControllerConfig[k] = v
   811  	}
   812  	icfg.Bootstrap = &BootstrapConfig{
   813  		StateInitializationParams: StateInitializationParams{
   814  			BootstrapMachineConstraints: cons,
   815  			ModelConstraints:            modelCons,
   816  		},
   817  	}
   818  	for k, v := range agentEnvironment {
   819  		if icfg.AgentEnvironment == nil {
   820  			icfg.AgentEnvironment = make(map[string]string)
   821  		}
   822  		icfg.AgentEnvironment[k] = v
   823  	}
   824  	icfg.Jobs = []model.MachineJob{
   825  		model.JobManageModel,
   826  		model.JobHostUnits,
   827  	}
   828  	return icfg, nil
   829  }
   830  
   831  // ProxyConfiguration encapsulates all proxy-related settings that can be used
   832  // to populate an InstanceConfig.
   833  type ProxyConfiguration struct {
   834  	// Legacy proxy settings.
   835  	Legacy proxy.Settings
   836  
   837  	// Juju-specific proxy settings.
   838  	Juju proxy.Settings
   839  
   840  	// Apt-specific proxy settings.
   841  	Apt proxy.Settings
   842  
   843  	// Snap-specific proxy settings.
   844  	Snap proxy.Settings
   845  
   846  	// Apt mirror.
   847  	AptMirror string
   848  
   849  	// SnapStoreAssertions contains a list of assertions that must be
   850  	// passed to snapd together with a store proxy ID parameter before it
   851  	// can connect to a snap store proxy.
   852  	SnapStoreAssertions string
   853  
   854  	// SnapStoreProxyID references a store entry in the snap store
   855  	// assertion list that must be passed to snapd before it can connect to
   856  	// a snap store proxy.
   857  	SnapStoreProxyID string
   858  
   859  	// SnapStoreProxyURL specifies the address of the snap store proxy. If
   860  	// specified instead of the assertions/storeID settings above, juju can
   861  	// directly contact the proxy to retrieve the assertions and store ID.
   862  	SnapStoreProxyURL string
   863  }
   864  
   865  // proxyConfigurationFromEnv populates a ProxyConfiguration object from an
   866  // environment Config value.
   867  func proxyConfigurationFromEnv(cfg *config.Config) ProxyConfiguration {
   868  	return ProxyConfiguration{
   869  		Legacy:              cfg.LegacyProxySettings(),
   870  		Juju:                cfg.JujuProxySettings(),
   871  		Apt:                 cfg.AptProxySettings(),
   872  		AptMirror:           cfg.AptMirror(),
   873  		Snap:                cfg.SnapProxySettings(),
   874  		SnapStoreAssertions: cfg.SnapStoreAssertions(),
   875  		SnapStoreProxyID:    cfg.SnapStoreProxy(),
   876  		SnapStoreProxyURL:   cfg.SnapStoreProxyURL(),
   877  	}
   878  }
   879  
   880  // PopulateInstanceConfig is called both from the FinishInstanceConfig below,
   881  // which does have access to the environment config, and from the container
   882  // provisioners, which don't have access to the environment config. Everything
   883  // that is needed to provision a container needs to be returned to the
   884  // provisioner in the ContainerConfig structure. Those values are then used to
   885  // call this function.
   886  func PopulateInstanceConfig(icfg *InstanceConfig,
   887  	providerType, authorizedKeys string,
   888  	sslHostnameVerification bool,
   889  	proxyCfg ProxyConfiguration,
   890  	enableOSRefreshUpdates bool,
   891  	enableOSUpgrade bool,
   892  	cloudInitUserData map[string]interface{},
   893  	profiles []string,
   894  ) error {
   895  	icfg.AuthorizedKeys = authorizedKeys
   896  	if icfg.AgentEnvironment == nil {
   897  		icfg.AgentEnvironment = make(map[string]string)
   898  	}
   899  	icfg.AgentEnvironment[agent.ProviderType] = providerType
   900  	icfg.AgentEnvironment[agent.ContainerType] = string(icfg.MachineContainerType)
   901  	icfg.DisableSSLHostnameVerification = !sslHostnameVerification
   902  	icfg.LegacyProxySettings = proxyCfg.Legacy
   903  	icfg.LegacyProxySettings.AutoNoProxy = strings.Join(icfg.APIHosts(), ",")
   904  	icfg.JujuProxySettings = proxyCfg.Juju
   905  	// No AutoNoProxy needed as juju no proxy values are CIDR aware.
   906  	icfg.AptProxySettings = proxyCfg.Apt
   907  	icfg.AptMirror = proxyCfg.AptMirror
   908  	icfg.SnapProxySettings = proxyCfg.Snap
   909  	icfg.SnapStoreAssertions = proxyCfg.SnapStoreAssertions
   910  	icfg.SnapStoreProxyID = proxyCfg.SnapStoreProxyID
   911  	icfg.SnapStoreProxyURL = proxyCfg.SnapStoreProxyURL
   912  	icfg.EnableOSRefreshUpdate = enableOSRefreshUpdates
   913  	icfg.EnableOSUpgrade = enableOSUpgrade
   914  	icfg.CloudInitUserData = cloudInitUserData
   915  	icfg.Profiles = profiles
   916  	return nil
   917  }
   918  
   919  // FinishInstanceConfig sets fields on a InstanceConfig that can be determined by
   920  // inspecting a plain config.Config and the machine constraints at the last
   921  // moment before creating the user-data. It assumes that the supplied Config comes
   922  // from an environment that has passed through all the validation checks in the
   923  // Bootstrap func, and that has set an agent-version (via finding the tools to,
   924  // use for bootstrap, or otherwise).
   925  // TODO(fwereade) This function is not meant to be "good" in any serious way:
   926  // it is better that this functionality be collected in one place here than
   927  // that it be spread out across 3 or 4 providers, but this is its only
   928  // redeeming feature.
   929  func FinishInstanceConfig(icfg *InstanceConfig, cfg *config.Config) (err error) {
   930  	defer errors.DeferredAnnotatef(&err, "cannot complete machine configuration")
   931  	if err := PopulateInstanceConfig(
   932  		icfg,
   933  		cfg.Type(),
   934  		cfg.AuthorizedKeys(),
   935  		cfg.SSLHostnameVerification(),
   936  		proxyConfigurationFromEnv(cfg),
   937  		cfg.EnableOSRefreshUpdate(),
   938  		cfg.EnableOSUpgrade(),
   939  		cfg.CloudInitUserData(),
   940  		nil,
   941  	); err != nil {
   942  		return errors.Trace(err)
   943  	}
   944  	if icfg.IsController() {
   945  		// Add NUMACTL preference. Needed to work for both bootstrap and high availability
   946  		// Only makes sense for controller,
   947  		logger.Debugf("Setting numa ctl preference to %v", icfg.ControllerConfig.NUMACtlPreference())
   948  		// Unfortunately, AgentEnvironment can only take strings as values
   949  		icfg.AgentEnvironment[agent.NUMACtlPreference] = fmt.Sprintf("%v", icfg.ControllerConfig.NUMACtlPreference())
   950  	}
   951  	return nil
   952  }
   953  
   954  // InstanceTags returns the minimum set of tags that should be set on a
   955  // machine instance, if the provider supports them.
   956  func InstanceTags(modelUUID, controllerUUID string, tagger tags.ResourceTagger, isController bool) map[string]string {
   957  	instanceTags := tags.ResourceTags(
   958  		names.NewModelTag(modelUUID),
   959  		names.NewControllerTag(controllerUUID),
   960  		tagger,
   961  	)
   962  	if isController {
   963  		instanceTags[tags.JujuIsController] = "true"
   964  	}
   965  	return instanceTags
   966  }