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