github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/agent/agent.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package agent 5 6 import ( 7 "bytes" 8 "fmt" 9 "io/ioutil" 10 "net" 11 "os" 12 "path" 13 "path/filepath" 14 "regexp" 15 "strconv" 16 "strings" 17 18 "github.com/juju/errors" 19 "github.com/juju/loggo" 20 "github.com/juju/names" 21 "github.com/juju/utils" 22 "github.com/juju/utils/series" 23 "github.com/juju/utils/shell" 24 25 "github.com/juju/juju/api" 26 "github.com/juju/juju/apiserver/params" 27 "github.com/juju/juju/juju/paths" 28 "github.com/juju/juju/mongo" 29 "github.com/juju/juju/network" 30 "github.com/juju/juju/state/multiwatcher" 31 "github.com/juju/juju/version" 32 ) 33 34 var logger = loggo.GetLogger("juju.agent") 35 36 // These are base values used for the corresponding defaults. 37 var ( 38 logDir = paths.MustSucceed(paths.LogDir(series.HostSeries())) 39 dataDir = paths.MustSucceed(paths.DataDir(series.HostSeries())) 40 confDir = paths.MustSucceed(paths.ConfDir(series.HostSeries())) 41 metricsSpoolDir = paths.MustSucceed(paths.MetricsSpoolDir(series.HostSeries())) 42 ) 43 44 // Agent exposes the agent's configuration to other components. This 45 // interface should probably be segregated (agent.ConfigGetter and 46 // agent.ConfigChanger?) but YAGNI *currently* advises against same. 47 type Agent interface { 48 49 // CurrentConfig returns a copy of the agent's configuration. No 50 // guarantees regarding ongoing correctness are made. 51 CurrentConfig() Config 52 53 // ChangeConfig allows clients to change the agent's configuration 54 // by supplying a callback that applies the changes. 55 ChangeConfig(ConfigMutator) error 56 } 57 58 // APIHostPortsSetter trivially wraps an Agent to implement 59 // worker/apiaddressupdater/APIAddressSetter. 60 type APIHostPortsSetter struct { 61 Agent 62 } 63 64 // SetAPIHostPorts is the APIAddressSetter interface. 65 func (s APIHostPortsSetter) SetAPIHostPorts(servers [][]network.HostPort) error { 66 return s.ChangeConfig(func(c ConfigSetter) error { 67 c.SetAPIHostPorts(servers) 68 return nil 69 }) 70 } 71 72 // SetStateServingInfo trivially wraps an Agent to implement 73 // worker/certupdater/SetStateServingInfo. 74 type StateServingInfoSetter struct { 75 Agent 76 } 77 78 // SetStateServingInfo is the SetStateServingInfo interface. 79 func (s StateServingInfoSetter) SetStateServingInfo(info params.StateServingInfo) error { 80 return s.ChangeConfig(func(c ConfigSetter) error { 81 c.SetStateServingInfo(info) 82 return nil 83 }) 84 } 85 86 // Paths holds the directory paths used by the agent. 87 type Paths struct { 88 // DataDir is the data directory where each agent has a subdirectory 89 // containing the configuration files. 90 DataDir string 91 // LogDir is the log directory where all logs from all agents on 92 // the machine are written. 93 LogDir string 94 // MetricsSpoolDir is the spool directory where workloads store 95 // collected metrics. 96 MetricsSpoolDir string 97 // ConfDir is the directory where all config file for 98 // Juju agents are stored. 99 ConfDir string 100 } 101 102 // Migrate assigns the directory locations specified from the new path configuration. 103 func (p *Paths) Migrate(newPaths Paths) { 104 if newPaths.DataDir != "" { 105 p.DataDir = newPaths.DataDir 106 } 107 if newPaths.LogDir != "" { 108 p.LogDir = newPaths.LogDir 109 } 110 if newPaths.MetricsSpoolDir != "" { 111 p.MetricsSpoolDir = newPaths.MetricsSpoolDir 112 } 113 if newPaths.ConfDir != "" { 114 p.ConfDir = newPaths.ConfDir 115 } 116 } 117 118 // NewPathsWithDefaults returns a Paths struct initialized with default locations if not otherwise specified. 119 func NewPathsWithDefaults(p Paths) Paths { 120 paths := DefaultPaths 121 if p.DataDir != "" { 122 paths.DataDir = p.DataDir 123 } 124 if p.LogDir != "" { 125 paths.LogDir = p.LogDir 126 } 127 if p.MetricsSpoolDir != "" { 128 paths.MetricsSpoolDir = p.MetricsSpoolDir 129 } 130 if p.ConfDir != "" { 131 paths.ConfDir = p.ConfDir 132 } 133 return paths 134 } 135 136 var ( 137 // DefaultPaths defines the default paths for an agent. 138 DefaultPaths = Paths{ 139 DataDir: dataDir, 140 LogDir: path.Join(logDir, "juju"), 141 MetricsSpoolDir: metricsSpoolDir, 142 ConfDir: confDir, 143 } 144 ) 145 146 // SystemIdentity is the name of the file where the environment SSH key is kept. 147 const SystemIdentity = "system-identity" 148 149 const ( 150 LxcBridge = "LXC_BRIDGE" 151 ProviderType = "PROVIDER_TYPE" 152 ContainerType = "CONTAINER_TYPE" 153 Namespace = "NAMESPACE" 154 StorageDir = "STORAGE_DIR" 155 StorageAddr = "STORAGE_ADDR" 156 AgentServiceName = "AGENT_SERVICE_NAME" 157 MongoOplogSize = "MONGO_OPLOG_SIZE" 158 NumaCtlPreference = "NUMA_CTL_PREFERENCE" 159 AllowsSecureConnection = "SECURE_STATESERVER_CONNECTION" 160 ) 161 162 // The Config interface is the sole way that the agent gets access to the 163 // configuration information for the machine and unit agents. There should 164 // only be one instance of a config object for any given agent, and this 165 // interface is passed between multiple go routines. The mutable methods are 166 // protected by a mutex, and it is expected that the caller doesn't modify any 167 // slice that may be returned. 168 // 169 // NOTE: should new mutating methods be added to this interface, consideration 170 // is needed around the synchronisation as a single instance is used in 171 // multiple go routines. 172 type Config interface { 173 // DataDir returns the data directory. Each agent has a subdirectory 174 // containing the configuration files. 175 DataDir() string 176 177 // LogDir returns the log directory. All logs from all agents on 178 // the machine are written to this directory. 179 LogDir() string 180 181 // SystemIdentityPath returns the path of the file where the environment 182 // SSH key is kept. 183 SystemIdentityPath() string 184 185 // Jobs returns a list of MachineJobs that need to run. 186 Jobs() []multiwatcher.MachineJob 187 188 // Tag returns the tag of the entity on whose behalf the state connection 189 // will be made. 190 Tag() names.Tag 191 192 // Dir returns the agent's directory. 193 Dir() string 194 195 // Nonce returns the nonce saved when the machine was provisioned 196 // TODO: make this one of the key/value pairs. 197 Nonce() string 198 199 // CACert returns the CA certificate that is used to validate the state or 200 // API server's certificate. 201 CACert() string 202 203 // APIAddresses returns the addresses needed to connect to the api server 204 APIAddresses() ([]string, error) 205 206 // WriteCommands returns shell commands to write the agent configuration. 207 // It returns an error if the configuration does not have all the right 208 // elements. 209 WriteCommands(renderer shell.Renderer) ([]string, error) 210 211 // StateServingInfo returns the details needed to run 212 // a state server and reports whether those details 213 // are available 214 StateServingInfo() (params.StateServingInfo, bool) 215 216 // APIInfo returns details for connecting to the API server. 217 APIInfo() *api.Info 218 219 // MongoInfo returns details for connecting to the state server's mongo 220 // database and reports whether those details are available 221 MongoInfo() (*mongo.MongoInfo, bool) 222 223 // OldPassword returns the fallback password when connecting to the 224 // API server. 225 OldPassword() string 226 227 // UpgradedToVersion returns the version for which all upgrade steps have been 228 // successfully run, which is also the same as the initially deployed version. 229 UpgradedToVersion() version.Number 230 231 // Value returns the value associated with the key, or an empty string if 232 // the key is not found. 233 Value(key string) string 234 235 // PreferIPv6 returns whether to prefer using IPv6 addresses (if 236 // available) when connecting to the state or API server. 237 PreferIPv6() bool 238 239 // Environment returns the tag for the environment that the agent belongs 240 // to. 241 Environment() names.EnvironTag 242 243 // MetricsSpoolDir returns the spool directory where workloads store 244 // collected metrics. 245 MetricsSpoolDir() string 246 } 247 248 type configSetterOnly interface { 249 // Clone returns a copy of the configuration that 250 // is unaffected by subsequent calls to the Set* 251 // methods 252 Clone() Config 253 254 // SetOldPassword sets the password that is currently 255 // valid but needs to be changed. This is used as 256 // a fallback. 257 SetOldPassword(oldPassword string) 258 259 // SetPassword sets the password to be used when 260 // connecting to the state. 261 SetPassword(newPassword string) 262 263 // SetValue updates the value for the specified key. 264 SetValue(key, value string) 265 266 // SetUpgradedToVersion sets the version that 267 // the agent has successfully upgraded to. 268 SetUpgradedToVersion(newVersion version.Number) 269 270 // SetAPIHostPorts sets the API host/port addresses to connect to. 271 SetAPIHostPorts(servers [][]network.HostPort) 272 273 // Migrate takes an existing agent config and applies the given 274 // parameters to change it. 275 // 276 // Only non-empty fields in newParams are used 277 // to change existing config settings. All changes are written 278 // atomically. UpgradedToVersion cannot be changed here, because 279 // Migrate is most likely called during an upgrade, so it will be 280 // changed at the end of the upgrade anyway, if successful. 281 // 282 // Migrate does not actually write the new configuration. 283 // 284 // Note that if the configuration file moves location, 285 // (if DataDir is set), the the caller is responsible for removing 286 // the old configuration. 287 Migrate(MigrateParams) error 288 289 // SetStateServingInfo sets the information needed 290 // to run a state server 291 SetStateServingInfo(info params.StateServingInfo) 292 } 293 294 // LogFileName returns the filename for the Agent's log file. 295 func LogFilename(c Config) string { 296 return filepath.Join(c.LogDir(), c.Tag().String()+".log") 297 } 298 299 type ConfigMutator func(ConfigSetter) error 300 301 type ConfigWriter interface { 302 // Write writes the agent configuration. 303 Write() error 304 } 305 306 type ConfigSetter interface { 307 Config 308 configSetterOnly 309 } 310 311 type ConfigSetterWriter interface { 312 Config 313 configSetterOnly 314 ConfigWriter 315 } 316 317 // MigrateParams holds agent config values to change in a 318 // Migrate call. Empty fields will be ignored. DeleteValues 319 // specifies a list of keys to delete. 320 type MigrateParams struct { 321 Paths Paths 322 Jobs []multiwatcher.MachineJob 323 DeleteValues []string 324 Values map[string]string 325 Environment names.EnvironTag 326 } 327 328 // Ensure that the configInternal struct implements the Config interface. 329 var _ Config = (*configInternal)(nil) 330 331 type connectionDetails struct { 332 addresses []string 333 password string 334 } 335 336 func (d *connectionDetails) clone() *connectionDetails { 337 if d == nil { 338 return nil 339 } 340 newd := *d 341 newd.addresses = append([]string{}, d.addresses...) 342 return &newd 343 } 344 345 type configInternal struct { 346 configFilePath string 347 paths Paths 348 tag names.Tag 349 nonce string 350 environment names.EnvironTag 351 jobs []multiwatcher.MachineJob 352 upgradedToVersion version.Number 353 caCert string 354 stateDetails *connectionDetails 355 apiDetails *connectionDetails 356 oldPassword string 357 servingInfo *params.StateServingInfo 358 values map[string]string 359 preferIPv6 bool 360 } 361 362 type AgentConfigParams struct { 363 Paths Paths 364 Jobs []multiwatcher.MachineJob 365 UpgradedToVersion version.Number 366 Tag names.Tag 367 Password string 368 Nonce string 369 Environment names.EnvironTag 370 StateAddresses []string 371 APIAddresses []string 372 CACert string 373 Values map[string]string 374 PreferIPv6 bool 375 } 376 377 // NewAgentConfig returns a new config object suitable for use for a 378 // machine or unit agent. 379 func NewAgentConfig(configParams AgentConfigParams) (ConfigSetterWriter, error) { 380 if configParams.Paths.DataDir == "" { 381 return nil, errors.Trace(requiredError("data directory")) 382 } 383 if configParams.Tag == nil { 384 return nil, errors.Trace(requiredError("entity tag")) 385 } 386 switch configParams.Tag.(type) { 387 case names.MachineTag, names.UnitTag: 388 // these are the only two type of tags that can represent an agent 389 default: 390 return nil, errors.Errorf("entity tag must be MachineTag or UnitTag, got %T", configParams.Tag) 391 } 392 if configParams.UpgradedToVersion == version.Zero { 393 return nil, errors.Trace(requiredError("upgradedToVersion")) 394 } 395 if configParams.Password == "" { 396 return nil, errors.Trace(requiredError("password")) 397 } 398 if uuid := configParams.Environment.Id(); uuid == "" { 399 return nil, errors.Trace(requiredError("environment")) 400 } else if !names.IsValidEnvironment(uuid) { 401 return nil, errors.Errorf("%q is not a valid environment uuid", uuid) 402 } 403 if len(configParams.CACert) == 0 { 404 return nil, errors.Trace(requiredError("CA certificate")) 405 } 406 // Note that the password parts of the state and api information are 407 // blank. This is by design. 408 config := &configInternal{ 409 paths: NewPathsWithDefaults(configParams.Paths), 410 jobs: configParams.Jobs, 411 upgradedToVersion: configParams.UpgradedToVersion, 412 tag: configParams.Tag, 413 nonce: configParams.Nonce, 414 environment: configParams.Environment, 415 caCert: configParams.CACert, 416 oldPassword: configParams.Password, 417 values: configParams.Values, 418 preferIPv6: configParams.PreferIPv6, 419 } 420 if len(configParams.StateAddresses) > 0 { 421 config.stateDetails = &connectionDetails{ 422 addresses: configParams.StateAddresses, 423 } 424 } 425 if len(configParams.APIAddresses) > 0 { 426 config.apiDetails = &connectionDetails{ 427 addresses: configParams.APIAddresses, 428 } 429 } 430 if err := config.check(); err != nil { 431 return nil, err 432 } 433 if config.values == nil { 434 config.values = make(map[string]string) 435 } 436 config.configFilePath = ConfigPath(config.paths.DataDir, config.tag) 437 return config, nil 438 } 439 440 // NewStateMachineConfig returns a configuration suitable for 441 // a machine running the state server. 442 func NewStateMachineConfig(configParams AgentConfigParams, serverInfo params.StateServingInfo) (ConfigSetterWriter, error) { 443 if serverInfo.Cert == "" { 444 return nil, errors.Trace(requiredError("state server cert")) 445 } 446 if serverInfo.PrivateKey == "" { 447 return nil, errors.Trace(requiredError("state server key")) 448 } 449 if serverInfo.CAPrivateKey == "" { 450 return nil, errors.Trace(requiredError("ca cert key")) 451 } 452 if serverInfo.StatePort == 0 { 453 return nil, errors.Trace(requiredError("state port")) 454 } 455 if serverInfo.APIPort == 0 { 456 return nil, errors.Trace(requiredError("api port")) 457 } 458 config, err := NewAgentConfig(configParams) 459 if err != nil { 460 return nil, err 461 } 462 config.SetStateServingInfo(serverInfo) 463 return config, nil 464 } 465 466 // Dir returns the agent-specific data directory. 467 func Dir(dataDir string, tag names.Tag) string { 468 // Note: must use path, not filepath, as this 469 // function is used by the client on Windows. 470 return path.Join(dataDir, "agents", tag.String()) 471 } 472 473 // ConfigPath returns the full path to the agent config file. 474 // NOTE: Delete this once all agents accept --config instead 475 // of --data-dir - it won't be needed anymore. 476 func ConfigPath(dataDir string, tag names.Tag) string { 477 return filepath.Join(Dir(dataDir, tag), agentConfigFilename) 478 } 479 480 // ReadConfig reads configuration data from the given location. 481 func ReadConfig(configFilePath string) (ConfigSetterWriter, error) { 482 var ( 483 format formatter 484 config *configInternal 485 ) 486 configData, err := ioutil.ReadFile(configFilePath) 487 if err != nil { 488 return nil, fmt.Errorf("cannot read agent config %q: %v", configFilePath, err) 489 } 490 491 // Try to read the legacy format file. 492 dir := filepath.Dir(configFilePath) 493 legacyFormatPath := filepath.Join(dir, legacyFormatFilename) 494 formatBytes, err := ioutil.ReadFile(legacyFormatPath) 495 if err != nil && !os.IsNotExist(err) { 496 return nil, fmt.Errorf("cannot read format file: %v", err) 497 } 498 formatData := string(formatBytes) 499 if err == nil { 500 // It exists, so unmarshal with a legacy formatter. 501 // Drop the format prefix to leave the version only. 502 if !strings.HasPrefix(formatData, legacyFormatPrefix) { 503 return nil, fmt.Errorf("malformed agent config format %q", formatData) 504 } 505 format, err = getFormatter(strings.TrimPrefix(formatData, legacyFormatPrefix)) 506 if err != nil { 507 return nil, err 508 } 509 config, err = format.unmarshal(configData) 510 } else { 511 // Does not exist, just parse the data. 512 format, config, err = parseConfigData(configData) 513 } 514 if err != nil { 515 return nil, err 516 } 517 logger.Debugf("read agent config, format %q", format.version()) 518 config.configFilePath = configFilePath 519 if format != currentFormat { 520 // Migrate from a legacy format to the new one. 521 err := config.Write() 522 if err != nil { 523 return nil, fmt.Errorf("cannot migrate %s agent config to %s: %v", format.version(), currentFormat.version(), err) 524 } 525 logger.Debugf("migrated agent config from %s to %s", format.version(), currentFormat.version()) 526 err = os.Remove(legacyFormatPath) 527 if err != nil && !os.IsNotExist(err) { 528 return nil, fmt.Errorf("cannot remove legacy format file %q: %v", legacyFormatPath, err) 529 } 530 } 531 return config, nil 532 } 533 534 func (c0 *configInternal) Clone() Config { 535 c1 := *c0 536 // Deep copy only fields which may be affected 537 // by ConfigSetter methods. 538 c1.stateDetails = c0.stateDetails.clone() 539 c1.apiDetails = c0.apiDetails.clone() 540 c1.jobs = append([]multiwatcher.MachineJob{}, c0.jobs...) 541 c1.values = make(map[string]string, len(c0.values)) 542 for key, val := range c0.values { 543 c1.values[key] = val 544 } 545 return &c1 546 } 547 548 func (config *configInternal) Migrate(newParams MigrateParams) error { 549 config.paths.Migrate(newParams.Paths) 550 config.configFilePath = ConfigPath(config.paths.DataDir, config.tag) 551 if len(newParams.Jobs) > 0 { 552 config.jobs = make([]multiwatcher.MachineJob, len(newParams.Jobs)) 553 copy(config.jobs, newParams.Jobs) 554 } 555 for _, key := range newParams.DeleteValues { 556 delete(config.values, key) 557 } 558 for key, value := range newParams.Values { 559 if config.values == nil { 560 config.values = make(map[string]string) 561 } 562 config.values[key] = value 563 } 564 if newParams.Environment.Id() != "" { 565 config.environment = newParams.Environment 566 } 567 if err := config.check(); err != nil { 568 return fmt.Errorf("migrated agent config is invalid: %v", err) 569 } 570 return nil 571 } 572 573 func (c *configInternal) SetUpgradedToVersion(newVersion version.Number) { 574 c.upgradedToVersion = newVersion 575 } 576 577 func (c *configInternal) SetAPIHostPorts(servers [][]network.HostPort) { 578 if c.apiDetails == nil { 579 return 580 } 581 var addrs []string 582 for _, serverHostPorts := range servers { 583 addr := network.SelectInternalHostPort(serverHostPorts, false) 584 if addr != "" { 585 addrs = append(addrs, addr) 586 } 587 } 588 c.apiDetails.addresses = addrs 589 } 590 591 func (c *configInternal) SetValue(key, value string) { 592 if value == "" { 593 delete(c.values, key) 594 } else { 595 c.values[key] = value 596 } 597 } 598 599 func (c *configInternal) SetOldPassword(oldPassword string) { 600 c.oldPassword = oldPassword 601 } 602 603 func (c *configInternal) SetPassword(newPassword string) { 604 if c.stateDetails != nil { 605 c.stateDetails.password = newPassword 606 } 607 if c.apiDetails != nil { 608 c.apiDetails.password = newPassword 609 } 610 } 611 612 func (c *configInternal) Write() error { 613 data, err := c.fileContents() 614 if err != nil { 615 return err 616 } 617 // Make sure the config dir gets created. 618 configDir := filepath.Dir(c.configFilePath) 619 if err := os.MkdirAll(configDir, 0755); err != nil { 620 return fmt.Errorf("cannot create agent config dir %q: %v", configDir, err) 621 } 622 return utils.AtomicWriteFile(c.configFilePath, data, 0600) 623 } 624 625 func requiredError(what string) error { 626 return fmt.Errorf("%s not found in configuration", what) 627 } 628 629 func (c *configInternal) File(name string) string { 630 return path.Join(c.Dir(), name) 631 } 632 633 func (c *configInternal) DataDir() string { 634 return c.paths.DataDir 635 } 636 637 func (c *configInternal) MetricsSpoolDir() string { 638 return c.paths.MetricsSpoolDir 639 } 640 641 func (c *configInternal) LogDir() string { 642 return c.paths.LogDir 643 } 644 645 func (c *configInternal) SystemIdentityPath() string { 646 return filepath.Join(c.paths.DataDir, SystemIdentity) 647 } 648 649 func (c *configInternal) Jobs() []multiwatcher.MachineJob { 650 return c.jobs 651 } 652 653 func (c *configInternal) Nonce() string { 654 return c.nonce 655 } 656 657 func (c *configInternal) UpgradedToVersion() version.Number { 658 return c.upgradedToVersion 659 } 660 661 func (c *configInternal) CACert() string { 662 return c.caCert 663 } 664 665 func (c *configInternal) Value(key string) string { 666 return c.values[key] 667 } 668 669 func (c *configInternal) PreferIPv6() bool { 670 return c.preferIPv6 671 } 672 673 func (c *configInternal) StateServingInfo() (params.StateServingInfo, bool) { 674 if c.servingInfo == nil { 675 return params.StateServingInfo{}, false 676 } 677 return *c.servingInfo, true 678 } 679 680 func (c *configInternal) SetStateServingInfo(info params.StateServingInfo) { 681 c.servingInfo = &info 682 } 683 684 func (c *configInternal) APIAddresses() ([]string, error) { 685 if c.apiDetails == nil { 686 return []string{}, errors.New("No apidetails in config") 687 } 688 return append([]string{}, c.apiDetails.addresses...), nil 689 } 690 691 func (c *configInternal) OldPassword() string { 692 return c.oldPassword 693 } 694 695 func (c *configInternal) Tag() names.Tag { 696 return c.tag 697 } 698 699 func (c *configInternal) Environment() names.EnvironTag { 700 return c.environment 701 } 702 703 func (c *configInternal) Dir() string { 704 return Dir(c.paths.DataDir, c.tag) 705 } 706 707 func (c *configInternal) check() error { 708 if c.stateDetails == nil && c.apiDetails == nil { 709 return errors.Trace(requiredError("state or API addresses")) 710 } 711 if c.stateDetails != nil { 712 if err := checkAddrs(c.stateDetails.addresses, "state server address"); err != nil { 713 return err 714 } 715 } 716 if c.apiDetails != nil { 717 if err := checkAddrs(c.apiDetails.addresses, "API server address"); err != nil { 718 return err 719 } 720 } 721 return nil 722 } 723 724 var validAddr = regexp.MustCompile("^.+:[0-9]+$") 725 726 func checkAddrs(addrs []string, what string) error { 727 if len(addrs) == 0 { 728 return errors.Trace(requiredError(what)) 729 } 730 for _, a := range addrs { 731 if !validAddr.MatchString(a) { 732 return errors.Errorf("invalid %s %q", what, a) 733 } 734 } 735 return nil 736 } 737 738 func (c *configInternal) fileContents() ([]byte, error) { 739 data, err := currentFormat.marshal(c) 740 if err != nil { 741 return nil, err 742 } 743 var buf bytes.Buffer 744 fmt.Fprintf(&buf, "%s%s\n", formatPrefix, currentFormat.version()) 745 buf.Write(data) 746 return buf.Bytes(), nil 747 } 748 749 func (c *configInternal) WriteCommands(renderer shell.Renderer) ([]string, error) { 750 data, err := c.fileContents() 751 if err != nil { 752 return nil, errors.Trace(err) 753 } 754 commands := renderer.MkdirAll(c.Dir()) 755 filename := c.File(agentConfigFilename) 756 commands = append(commands, renderer.WriteFile(filename, data)...) 757 commands = append(commands, renderer.Chmod(filename, 0600)...) 758 return commands, nil 759 } 760 761 func (c *configInternal) APIInfo() *api.Info { 762 servingInfo, isStateServer := c.StateServingInfo() 763 addrs := c.apiDetails.addresses 764 if isStateServer { 765 port := servingInfo.APIPort 766 localAPIAddr := net.JoinHostPort("localhost", strconv.Itoa(port)) 767 if c.preferIPv6 { 768 localAPIAddr = net.JoinHostPort("::1", strconv.Itoa(port)) 769 } 770 addrInAddrs := false 771 for _, addr := range addrs { 772 if addr == localAPIAddr { 773 addrInAddrs = true 774 break 775 } 776 } 777 if !addrInAddrs { 778 addrs = append(addrs, localAPIAddr) 779 } 780 } 781 return &api.Info{ 782 Addrs: addrs, 783 Password: c.apiDetails.password, 784 CACert: c.caCert, 785 Tag: c.tag, 786 Nonce: c.nonce, 787 EnvironTag: c.environment, 788 } 789 } 790 791 func (c *configInternal) MongoInfo() (info *mongo.MongoInfo, ok bool) { 792 ssi, ok := c.StateServingInfo() 793 if !ok { 794 return nil, false 795 } 796 addr := net.JoinHostPort("127.0.0.1", strconv.Itoa(ssi.StatePort)) 797 if c.preferIPv6 { 798 addr = net.JoinHostPort("::1", strconv.Itoa(ssi.StatePort)) 799 } 800 return &mongo.MongoInfo{ 801 Info: mongo.Info{ 802 Addrs: []string{addr}, 803 CACert: c.caCert, 804 }, 805 Password: c.stateDetails.password, 806 Tag: c.tag, 807 }, true 808 }