launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/worker/uniter/uniter.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter 5 6 import ( 7 "fmt" 8 "math/rand" 9 "os" 10 "path/filepath" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/loggo/loggo" 16 "launchpad.net/errgo/errors" 17 "launchpad.net/tomb" 18 19 "launchpad.net/juju-core/agent/tools" 20 corecharm "launchpad.net/juju-core/charm" 21 "launchpad.net/juju-core/charm/hooks" 22 "launchpad.net/juju-core/cmd" 23 "launchpad.net/juju-core/environs/config" 24 "launchpad.net/juju-core/juju/osenv" 25 "launchpad.net/juju-core/state/api/params" 26 "launchpad.net/juju-core/state/api/uniter" 27 apiwatcher "launchpad.net/juju-core/state/api/watcher" 28 "launchpad.net/juju-core/state/watcher" 29 "launchpad.net/juju-core/utils" 30 "launchpad.net/juju-core/utils/exec" 31 "launchpad.net/juju-core/utils/fslock" 32 "launchpad.net/juju-core/worker" 33 "launchpad.net/juju-core/worker/uniter/charm" 34 ucharm "launchpad.net/juju-core/worker/uniter/charm" 35 "launchpad.net/juju-core/worker/uniter/hook" 36 "launchpad.net/juju-core/worker/uniter/jujuc" 37 "launchpad.net/juju-core/worker/uniter/relation" 38 ) 39 40 var mask = errors.MaskFunc( 41 params.IsCodeNotFound, 42 params.IsCodeUnauthorized, 43 errors.Is(worker.ErrTerminateAgent), 44 errors.Is(ucharm.ErrConflict), 45 ) 46 47 var logger = loggo.GetLogger("juju.worker.uniter") 48 49 const ( 50 // These work fine for linux, but should we need to work with windows 51 // workloads in the future, we'll need to move these into a file that is 52 // compiled conditionally for different targets and use tcp (most likely). 53 RunListenerNetType = "unix" 54 RunListenerFile = "run.socket" 55 ) 56 57 // A UniterExecutionObserver gets the appropriate methods called when a hook 58 // is executed and either succeeds or fails. Missing hooks don't get reported 59 // in this way. 60 type UniterExecutionObserver interface { 61 HookCompleted(hookName string) 62 HookFailed(hookName string) 63 } 64 65 // Uniter implements the capabilities of the unit agent. It is not intended to 66 // implement the actual *behaviour* of the unit agent; that responsibility is 67 // delegated to Mode values, which are expected to react to events and direct 68 // the uniter's responses to them. 69 type Uniter struct { 70 tomb tomb.Tomb 71 st *uniter.State 72 f *filter 73 unit *uniter.Unit 74 service *uniter.Service 75 relationers map[int]*Relationer 76 relationHooks chan hook.Info 77 uuid string 78 envName string 79 80 dataDir string 81 baseDir string 82 toolsDir string 83 relationsDir string 84 charm *charm.GitDir 85 bundles *charm.BundlesDir 86 deployer *charm.Deployer 87 s *State 88 sf *StateFile 89 rand *rand.Rand 90 hookLock *fslock.Lock 91 runListener *RunListener 92 93 proxy osenv.ProxySettings 94 proxyMutex sync.Mutex 95 96 ranConfigChanged bool 97 // The execution observer is only used in tests at this stage. Should this 98 // need to be extended, perhaps a list of observers would be needed. 99 observer UniterExecutionObserver 100 } 101 102 // NewUniter creates a new Uniter which will install, run, and upgrade 103 // a charm on behalf of the unit with the given unitTag, by executing 104 // hooks and operations provoked by changes in st. 105 func NewUniter(st *uniter.State, unitTag string, dataDir string) *Uniter { 106 u := &Uniter{ 107 st: st, 108 dataDir: dataDir, 109 } 110 go func() { 111 defer u.tomb.Done() 112 u.tomb.Kill(u.loop(unitTag)) 113 }() 114 return u 115 } 116 117 func (u *Uniter) loop(unitTag string) (err error) { 118 if err = u.init(unitTag); err != nil { 119 return mask(err) 120 } 121 defer u.runListener.Close() 122 logger.Infof("unit %q started", u.unit) 123 124 environWatcher, err := u.st.WatchForEnvironConfigChanges() 125 if err != nil { 126 return mask(err) 127 } 128 defer watcher.Stop(environWatcher, &u.tomb) 129 u.watchForProxyChanges(environWatcher) 130 131 // Start filtering state change events for consumption by modes. 132 u.f, err = newFilter(u.st, unitTag) 133 if err != nil { 134 return mask(err) 135 } 136 defer watcher.Stop(u.f, &u.tomb) 137 go func() { 138 u.tomb.Kill(u.f.Wait()) 139 }() 140 141 // Run modes until we encounter an error. 142 mode := ModeInit 143 for err == nil { 144 select { 145 case <-u.tomb.Dying(): 146 err = tomb.ErrDying 147 default: 148 mode, err = mode(u) 149 } 150 } 151 logger.Infof("unit %q shutting down: %s", u.unit, err) 152 return err 153 } 154 155 func (u *Uniter) setupLocks() (err error) { 156 lockDir := filepath.Join(u.dataDir, "locks") 157 u.hookLock, err = fslock.NewLock(lockDir, "uniter-hook-execution") 158 if err != nil { 159 return mask(err) 160 } 161 if message := u.hookLock.Message(); u.hookLock.IsLocked() && message != "" { 162 // Look to see if it was us that held the lock before. If it was, we 163 // should be safe enough to break it, as it is likely that we died 164 // before unlocking, and have been restarted by upstart. 165 parts := strings.SplitN(message, ":", 2) 166 if len(parts) > 1 && parts[0] == u.unit.Name() { 167 if err := u.hookLock.BreakLock(); err != nil { 168 return mask(err) 169 } 170 } 171 } 172 return nil 173 } 174 175 func (u *Uniter) init(unitTag string) (err error) { 176 defer utils.ErrorContextf(&err, "failed to initialize uniter for %q", unitTag) 177 u.unit, err = u.st.Unit(unitTag) 178 if err != nil { 179 return mask(err) 180 } 181 if err = u.setupLocks(); err != nil { 182 return mask(err) 183 } 184 u.toolsDir = tools.ToolsDir(u.dataDir, unitTag) 185 if err := EnsureJujucSymlinks(u.toolsDir); err != nil { 186 return mask(err) 187 } 188 u.baseDir = filepath.Join(u.dataDir, "agents", unitTag) 189 u.relationsDir = filepath.Join(u.baseDir, "state", "relations") 190 if err := os.MkdirAll(u.relationsDir, 0755); err != nil { 191 return mask(err) 192 } 193 u.service, err = u.st.Service(u.unit.ServiceTag()) 194 if err != nil { 195 return mask(err) 196 } 197 var env *uniter.Environment 198 env, err = u.st.Environment() 199 if err != nil { 200 return mask(err) 201 } 202 u.uuid = env.UUID() 203 u.envName = env.Name() 204 205 runListenerSocketPath := filepath.Join(u.baseDir, RunListenerFile) 206 logger.Debugf("starting juju-run listener on %s:%s", RunListenerNetType, runListenerSocketPath) 207 u.runListener, err = NewRunListener(u, RunListenerNetType, runListenerSocketPath) 208 if err != nil { 209 return mask(err) 210 } 211 212 // The socket needs to have permissions 777 in order for other users to use it. 213 if err := os.Chmod(runListenerSocketPath, 0777); err != nil { 214 return mask(err) 215 } 216 u.relationers = map[int]*Relationer{} 217 u.relationHooks = make(chan hook.Info) 218 u.charm = charm.NewGitDir(filepath.Join(u.baseDir, "charm")) 219 u.bundles = charm.NewBundlesDir(filepath.Join(u.baseDir, "state", "bundles")) 220 u.deployer = charm.NewDeployer(filepath.Join(u.baseDir, "state", "deployer")) 221 u.sf = NewStateFile(filepath.Join(u.baseDir, "state", "uniter")) 222 u.rand = rand.New(rand.NewSource(time.Now().Unix())) 223 return nil 224 } 225 226 func (u *Uniter) Kill() { 227 u.tomb.Kill(nil) 228 } 229 230 func (u *Uniter) Wait() error { 231 return u.tomb.Wait() 232 } 233 234 func (u *Uniter) Stop() error { 235 u.tomb.Kill(nil) 236 return u.Wait() 237 } 238 239 func (u *Uniter) Dead() <-chan struct{} { 240 return u.tomb.Dead() 241 } 242 243 // writeState saves uniter state with the supplied values, and infers the appropriate 244 // value of Started. 245 func (u *Uniter) writeState(op Op, step OpStep, hi *hook.Info, url *corecharm.URL) error { 246 s := State{ 247 Started: op == RunHook && hi.Kind == hooks.Start || u.s != nil && u.s.Started, 248 Op: op, 249 OpStep: step, 250 Hook: hi, 251 CharmURL: url, 252 } 253 if err := u.sf.Write(s.Started, s.Op, s.OpStep, s.Hook, s.CharmURL); err != nil { 254 return mask(err) 255 } 256 u.s = &s 257 return nil 258 } 259 260 // deploy deploys the supplied charm URL, and sets follow-up hook operation state 261 // as indicated by reason. 262 func (u *Uniter) deploy(curl *corecharm.URL, reason Op) error { 263 if reason != Install && reason != Upgrade { 264 panic(errors.Newf("%q is not a deploy operation", reason)) 265 } 266 var hi *hook.Info 267 if u.s != nil && (u.s.Op == RunHook || u.s.Op == Upgrade) { 268 // If this upgrade interrupts a RunHook, we need to preserve the hook 269 // info so that we can return to the appropriate error state. However, 270 // if we're resuming (or have force-interrupted) an Upgrade, we also 271 // need to preserve whatever hook info was preserved when we initially 272 // started upgrading, to ensure we still return to the correct state. 273 hi = u.s.Hook 274 } 275 if u.s == nil || u.s.OpStep != Done { 276 // Get the new charm bundle before announcing intention to use it. 277 logger.Infof("fetching charm %q", curl) 278 sch, err := u.st.Charm(curl) 279 if err != nil { 280 return mask(err) 281 } 282 bun, err := u.bundles.Read(sch, u.tomb.Dying()) 283 if err != nil { 284 return mask(err) 285 } 286 if err = u.deployer.Stage(bun, curl); err != nil { 287 return mask(err) 288 } 289 290 // Set the new charm URL - this returns when the operation is complete, 291 // at which point we can refresh the local copy of the unit to get a 292 // version with the correct charm URL, and can go ahead and deploy 293 // the charm proper. 294 if err := u.f.SetCharm(curl); err != nil { 295 return mask(err) 296 } 297 if err := u.unit.Refresh(); err != nil { 298 return mask(err) 299 } 300 logger.Infof("deploying charm %q", curl) 301 if err = u.writeState(reason, Pending, hi, curl); err != nil { 302 return mask(err) 303 } 304 if err = u.deployer.Deploy(u.charm); err != nil { 305 return mask(err) 306 } 307 if err = u.writeState(reason, Done, hi, curl); err != nil { 308 return mask(err) 309 } 310 } 311 logger.Infof("charm %q is deployed", curl) 312 status := Queued 313 if hi != nil { 314 // If a hook operation was interrupted, restore it. 315 status = Pending 316 } else { 317 // Otherwise, queue the relevant post-deploy hook. 318 hi = &hook.Info{} 319 switch reason { 320 case Install: 321 hi.Kind = hooks.Install 322 case Upgrade: 323 hi.Kind = hooks.UpgradeCharm 324 } 325 } 326 return u.writeState(RunHook, status, hi, nil) 327 } 328 329 // errHookFailed indicates that a hook failed to execute, but that the Uniter's 330 // operation is not affected by the error. 331 var errHookFailed = errors.New("hook execution failed") 332 333 func (u *Uniter) getHookContext(hctxId string, relationId int, remoteUnitName string) (context *HookContext, err error) { 334 335 apiAddrs, err := u.st.APIAddresses() 336 if err != nil { 337 return nil, mask(err) 338 } 339 ownerTag, err := u.service.GetOwnerTag() 340 if err != nil { 341 return nil, mask(err) 342 } 343 ctxRelations := map[int]*ContextRelation{} 344 for id, r := range u.relationers { 345 ctxRelations[id] = r.Context() 346 } 347 348 u.proxyMutex.Lock() 349 defer u.proxyMutex.Unlock() 350 351 // Make a copy of the proxy settings. 352 proxySettings := u.proxy 353 return NewHookContext(u.unit, hctxId, u.uuid, u.envName, relationId, 354 remoteUnitName, ctxRelations, apiAddrs, ownerTag, proxySettings) 355 } 356 357 func (u *Uniter) acquireHookLock(message string) (err error) { 358 // We want to make sure we don't block forever when locking, but take the 359 // tomb into account. 360 checkTomb := func() error { 361 select { 362 case <-u.tomb.Dying(): 363 return tomb.ErrDying 364 default: 365 // no-op to fall through to return. 366 } 367 return nil 368 } 369 if err = u.hookLock.LockWithFunc(message, checkTomb); err != nil { 370 return mask(err) 371 } 372 return nil 373 } 374 375 func (u *Uniter) startJujucServer(context *HookContext) (*jujuc.Server, string, error) { 376 // Prepare server. 377 getCmd := func(ctxId, cmdName string) (cmd.Command, error) { 378 // TODO: switch to long-running server with single context; 379 // use nonce in place of context id. 380 if ctxId != context.id { 381 return nil, errors.Newf("expected context id %q, got %q", context.id, ctxId) 382 } 383 return jujuc.NewCommand(context, cmdName) 384 } 385 socketPath := filepath.Join(u.baseDir, "agent.socket") 386 // Use abstract namespace so we don't get stale socket files. 387 socketPath = "@" + socketPath 388 srv, err := jujuc.NewServer(getCmd, socketPath) 389 if err != nil { 390 return nil, "", mask(err) 391 } 392 go srv.Run() 393 return srv, socketPath, nil 394 } 395 396 // RunCommands executes the supplied commands in a hook context. 397 func (u *Uniter) RunCommands(commands string) (results *exec.ExecResponse, err error) { 398 logger.Tracef("run commands: %s", commands) 399 hctxId := fmt.Sprintf("%s:run-commands:%d", u.unit.Name(), u.rand.Int63()) 400 lockMessage := fmt.Sprintf("%s: running commands", u.unit.Name()) 401 if err = u.acquireHookLock(lockMessage); err != nil { 402 return nil, mask(err) 403 } 404 defer u.hookLock.Unlock() 405 406 hctx, err := u.getHookContext(hctxId, -1, "") 407 if err != nil { 408 return nil, mask(err) 409 } 410 srv, socketPath, err := u.startJujucServer(hctx) 411 if err != nil { 412 return nil, mask(err) 413 } 414 defer srv.Close() 415 416 result, err := hctx.RunCommands(commands, u.charm.Path(), u.toolsDir, socketPath) 417 if result != nil { 418 logger.Tracef("run commands: rc=%v\nstdout:\n%sstderr:\n%s", result.Code, result.Stdout, result.Stderr) 419 } 420 return result, err 421 } 422 423 func (u *Uniter) notifyHookInternal(hook string, hctx *HookContext, method func(string)) { 424 if r, ok := hctx.HookRelation(); ok { 425 remote, _ := hctx.RemoteUnitName() 426 if remote != "" { 427 remote = " " + remote 428 } 429 hook = hook + remote + " " + r.FakeId() 430 } 431 method(hook) 432 } 433 434 func (u *Uniter) notifyHookCompleted(hook string, hctx *HookContext) { 435 if u.observer != nil { 436 u.notifyHookInternal(hook, hctx, u.observer.HookCompleted) 437 } 438 } 439 440 func (u *Uniter) notifyHookFailed(hook string, hctx *HookContext) { 441 if u.observer != nil { 442 u.notifyHookInternal(hook, hctx, u.observer.HookFailed) 443 } 444 } 445 446 // runHook executes the supplied hook.Info in an appropriate hook context. If 447 // the hook itself fails to execute, it returns errHookFailed. 448 func (u *Uniter) runHook(hi hook.Info) (err error) { 449 // Prepare context. 450 if err = hi.Validate(); err != nil { 451 return mask(err) 452 } 453 454 hookName := string(hi.Kind) 455 relationId := -1 456 if hi.Kind.IsRelation() { 457 relationId = hi.RelationId 458 if hookName, err = u.relationers[relationId].PrepareHook(hi); err != nil { 459 return mask(err) 460 } 461 } 462 hctxId := fmt.Sprintf("%s:%s:%d", u.unit.Name(), hookName, u.rand.Int63()) 463 464 lockMessage := fmt.Sprintf("%s: running hook %q", u.unit.Name(), hookName) 465 if err = u.acquireHookLock(lockMessage); err != nil { 466 return mask(err) 467 } 468 defer u.hookLock.Unlock() 469 470 hctx, err := u.getHookContext(hctxId, relationId, hi.RemoteUnit) 471 if err != nil { 472 return mask(err) 473 } 474 srv, socketPath, err := u.startJujucServer(hctx) 475 if err != nil { 476 return mask(err) 477 } 478 defer srv.Close() 479 480 // Run the hook. 481 if err := u.writeState(RunHook, Pending, &hi, nil); err != nil { 482 return mask(err) 483 } 484 logger.Infof("running %q hook", hookName) 485 ranHook := true 486 err = hctx.RunHook(hookName, u.charm.Path(), u.toolsDir, socketPath) 487 if IsMissingHookError(err) { 488 ranHook = false 489 } else if err != nil { 490 logger.Errorf("hook failed: %s", err) 491 u.notifyHookFailed(hookName, hctx) 492 return errHookFailed 493 } 494 if err := u.writeState(RunHook, Done, &hi, nil); err != nil { 495 return mask(err) 496 } 497 if ranHook { 498 logger.Infof("ran %q hook", hookName) 499 u.notifyHookCompleted(hookName, hctx) 500 } else { 501 logger.Infof("skipped %q hook (missing)", hookName) 502 } 503 return u.commitHook(hi) 504 } 505 506 // commitHook ensures that state is consistent with the supplied hook, and 507 // that the fact of the hook's completion is persisted. 508 func (u *Uniter) commitHook(hi hook.Info) error { 509 logger.Infof("committing %q hook", hi.Kind) 510 if hi.Kind.IsRelation() { 511 if err := u.relationers[hi.RelationId].CommitHook(hi); err != nil { 512 return mask(err) 513 } 514 if hi.Kind == hooks.RelationBroken { 515 delete(u.relationers, hi.RelationId) 516 } 517 } 518 if err := u.charm.Snapshotf("Completed %q hook.", hi.Kind); err != nil { 519 return mask(err) 520 } 521 if hi.Kind == hooks.ConfigChanged { 522 u.ranConfigChanged = true 523 } 524 if err := u.writeState(Continue, Pending, &hi, nil); err != nil { 525 return mask(err) 526 } 527 logger.Infof("committed %q hook", hi.Kind) 528 return nil 529 } 530 531 // currentHookName returns the current full hook name. 532 func (u *Uniter) currentHookName() string { 533 hookInfo := u.s.Hook 534 hookName := string(hookInfo.Kind) 535 if hookInfo.Kind.IsRelation() { 536 relationer := u.relationers[hookInfo.RelationId] 537 name := relationer.ru.Endpoint().Name 538 hookName = fmt.Sprintf("%s-%s", name, hookInfo.Kind) 539 } 540 return hookName 541 } 542 543 // restoreRelations reconciles the supplied relation state dirs with the 544 // remote state of the corresponding relations. 545 func (u *Uniter) restoreRelations() error { 546 // TODO(dimitern): Get these from state, not from disk. 547 dirs, err := relation.ReadAllStateDirs(u.relationsDir) 548 if err != nil { 549 return mask(err) 550 } 551 for id, dir := range dirs { 552 remove := false 553 rel, err := u.st.RelationById(id) 554 if params.IsCodeNotFoundOrCodeUnauthorized(err) { 555 remove = true 556 } else if err != nil { 557 return mask(err) 558 } 559 err = u.addRelation(rel, dir) 560 if params.IsCodeCannotEnterScope(err) { 561 remove = true 562 } else if err != nil { 563 return mask(err) 564 } 565 if remove { 566 // If the previous execution was interrupted in the process of 567 // joining or departing the relation, the directory will be empty 568 // and the state is sane. 569 if err := dir.Remove(); err != nil { 570 return errors.Notef(err, "cannot synchronize relation state") 571 } 572 } 573 } 574 return nil 575 } 576 577 // updateRelations responds to changes in the life states of the relations 578 // with the supplied ids. If any id corresponds to an alive relation not 579 // known to the unit, the uniter will join that relation and return its 580 // relationer in the added list. 581 func (u *Uniter) updateRelations(ids []int) (added []*Relationer, err error) { 582 for _, id := range ids { 583 if r, found := u.relationers[id]; found { 584 rel := r.ru.Relation() 585 if err := rel.Refresh(); err != nil { 586 return nil, errors.Notef(err, "cannot update relation %q", rel) 587 } 588 if rel.Life() == params.Dying { 589 if err := r.SetDying(); err != nil { 590 return nil, mask(err) 591 } else if r.IsImplicit() { 592 delete(u.relationers, id) 593 } 594 } 595 continue 596 } 597 // Relations that are not alive are simply skipped, because they 598 // were not previously known anyway. 599 rel, err := u.st.RelationById(id) 600 if err != nil { 601 if params.IsCodeNotFoundOrCodeUnauthorized(err) { 602 continue 603 } 604 return nil, err 605 } 606 if rel.Life() != params.Alive { 607 continue 608 } 609 // Make sure we ignore relations not implemented by the unit's charm 610 ch, err := corecharm.ReadDir(u.charm.Path()) 611 if err != nil { 612 return nil, mask(err) 613 } 614 if ep, err := rel.Endpoint(); err != nil { 615 return nil, mask(err) 616 } else if !ep.ImplementedBy(ch) { 617 logger.Warningf("skipping relation with unknown endpoint %q", ep.Name) 618 continue 619 } 620 dir, err := relation.ReadStateDir(u.relationsDir, id) 621 if err != nil { 622 return nil, mask(err) 623 } 624 err = u.addRelation(rel, dir) 625 if err == nil { 626 added = append(added, u.relationers[id]) 627 continue 628 } 629 e := dir.Remove() 630 if !params.IsCodeCannotEnterScope(err) { 631 return nil, err 632 } 633 if e != nil { 634 return nil, e 635 } 636 } 637 if ok, err := u.unit.IsPrincipal(); err != nil { 638 return nil, mask(err) 639 } else if ok { 640 return added, nil 641 } 642 // If no Alive relations remain between a subordinate unit's service 643 // and its principal's service, the subordinate must become Dying. 644 keepAlive := false 645 for _, r := range u.relationers { 646 scope := r.ru.Endpoint().Scope 647 if scope == corecharm.ScopeContainer && !r.dying { 648 keepAlive = true 649 break 650 } 651 } 652 if !keepAlive { 653 if err := u.unit.Destroy(); err != nil { 654 return nil, mask(err) 655 } 656 } 657 return added, nil 658 } 659 660 // addRelation causes the unit agent to join the supplied relation, and to 661 // store persistent state in the supplied dir. 662 func (u *Uniter) addRelation(rel *uniter.Relation, dir *relation.StateDir) error { 663 logger.Infof("joining relation %q", rel) 664 ru, err := rel.Unit(u.unit) 665 if err != nil { 666 return mask(err) 667 } 668 r := NewRelationer(ru, dir, u.relationHooks) 669 w, err := u.unit.Watch() 670 if err != nil { 671 return mask(err) 672 } 673 defer watcher.Stop(w, &u.tomb) 674 for { 675 select { 676 case <-u.tomb.Dying(): 677 return tomb.ErrDying 678 case _, ok := <-w.Changes(): 679 if !ok { 680 return watcher.MustErr(w) 681 } 682 err := r.Join() 683 if params.IsCodeCannotEnterScopeYet(err) { 684 logger.Infof("cannot enter scope for relation %q; waiting for subordinate to be removed", rel) 685 continue 686 } else if err != nil { 687 return mask(err) 688 } 689 logger.Infof("joined relation %q", rel) 690 u.relationers[rel.Id()] = r 691 return nil 692 } 693 } 694 } 695 696 // updatePackageProxy updates the package proxy settings from the 697 // environment. 698 func (u *Uniter) updatePackageProxy(cfg *config.Config) { 699 u.proxyMutex.Lock() 700 defer u.proxyMutex.Unlock() 701 702 newSettings := cfg.ProxySettings() 703 if u.proxy != newSettings { 704 u.proxy = newSettings 705 logger.Debugf("Updated proxy settings: %#v", u.proxy) 706 // Update the environment values used by the process. 707 u.proxy.SetEnvironmentValues() 708 } 709 } 710 711 // watchForProxyChanges kicks off a go routine to listen to the watcher and 712 // update the proxy settings. 713 func (u *Uniter) watchForProxyChanges(environWatcher apiwatcher.NotifyWatcher) { 714 go func() { 715 for { 716 select { 717 case <-u.tomb.Dying(): 718 return 719 case _, ok := <-environWatcher.Changes(): 720 logger.Debugf("new environment change") 721 if !ok { 722 return 723 } 724 environConfig, err := u.st.EnvironConfig() 725 if err != nil { 726 logger.Errorf("cannot load environment configuration: %v", err) 727 } else { 728 u.updatePackageProxy(environConfig) 729 } 730 } 731 } 732 }() 733 }