// start is a method on ManifoldConfig because it's more readable than a closure. func (config ManifoldConfig) start(getResource dependency.GetResourceFunc) (worker.Worker, error) { var clock clock.Clock if err := getResource(config.ClockName, &clock); err != nil { return nil, errors.Trace(err) } var apiCaller base.APICaller if err := getResource(config.APICallerName, &apiCaller); err != nil { return nil, errors.Trace(err) } var agent agent.Agent if err := getResource(config.AgentName, &agent); err != nil { return nil, errors.Trace(err) } agentTag := agent.CurrentConfig().Tag() machineTag, ok := agentTag.(names.MachineTag) if !ok { return nil, errors.New("singular flag expected a machine agent") } // TODO(fwereade): model id is implicit in apiCaller, would // be better if explicit. facade, err := config.NewFacade(apiCaller, machineTag) if err != nil { return nil, errors.Trace(err) } flag, err := config.NewWorker(FlagConfig{ Clock: clock, Facade: facade, Duration: config.Duration, }) if err != nil { return nil, errors.Trace(err) } return flag, nil }
// newWorker trivially wraps NewWorker for use in a engine.AgentAPIManifold. func newWorker(a agent.Agent, apiCaller base.APICaller) (worker.Worker, error) { cfg := a.CurrentConfig() // Grab the tag and ensure that it's for a machine. tag, ok := cfg.Tag().(names.MachineTag) if !ok { return nil, errors.New("this manifold may only be used inside a machine agent") } // Get the machine agent's jobs. entity, err := apiagent.NewState(apiCaller).Entity(tag) if err != nil { return nil, err } var isModelManager bool for _, job := range entity.Jobs() { if job == multiwatcher.JobManageModel { isModelManager = true break } } if !isModelManager { return nil, dependency.ErrMissing } return NewWorker(cfg) }
func newWorker(a agent.Agent, apiCaller base.APICaller) (worker.Worker, error) { cfg := a.CurrentConfig() // Grab the tag and ensure that it's for a machine. tag, ok := cfg.Tag().(names.MachineTag) if !ok { return nil, errors.New("this manifold may only be used inside a machine agent") } // Get the machine agent's jobs. // TODO(fwereade): this functionality should be on the // deployer facade instead. agentFacade := apiagent.NewState(apiCaller) entity, err := agentFacade.Entity(tag) if err != nil { return nil, err } var isModelManager bool for _, job := range entity.Jobs() { if job == multiwatcher.JobManageModel { isModelManager = true break } } if !isModelManager { return nil, dependency.ErrMissing } return NewResumer(apiresumer.NewAPI(apiCaller)), nil }
// NewLock creates a gate.Lock to be used to synchronise workers which // need to start after upgrades have completed. If no upgrade steps // are required the Lock is unlocked and the version in agent's // configuration is updated to the currently running version. // // The returned Lock should be passed to NewWorker. func NewLock(a agent.Agent) (gate.Lock, error) { lock := gate.NewLock() if wrench.IsActive("machine-agent", "always-try-upgrade") { // Always enter upgrade mode. This allows test of upgrades // even when there's actually no upgrade steps to run. return lock, nil } err := a.ChangeConfig(func(agentConfig agent.ConfigSetter) error { if !upgrades.AreUpgradesDefined(agentConfig.UpgradedToVersion()) { logger.Infof("no upgrade steps required or upgrade steps for %v "+ "have already been run.", jujuversion.Current) lock.Unlock() // Even if no upgrade is required the version number in // the agent's config still needs to be bumped. agentConfig.SetUpgradedToVersion(jujuversion.Current) } return nil }) if err != nil { return nil, err } return lock, nil }
// Manifold returns a dependency.Manifold which wraps the machine // agent's voyeur.Value which gets set whenever it the machine agent's // config is changed. Whenever the config is updated the presence of // state serving info is checked and if state serving info was added // or removed the manifold worker will bounce itself. // // The manifold offes a single boolean output which will be true if // state serving info is available (i.e. the machine agent should be a // state server) and false otherwise. // // This manifold is intended to be used as a dependency for the state // manifold. func Manifold(config ManifoldConfig) dependency.Manifold { return dependency.Manifold{ Inputs: []string{config.AgentName}, Start: func(getResource dependency.GetResourceFunc) (worker.Worker, error) { var a agent.Agent if err := getResource(config.AgentName, &a); err != nil { return nil, err } if config.AgentConfigChanged == nil { return nil, errors.NotValidf("nil AgentConfigChanged") } if _, ok := a.CurrentConfig().Tag().(names.MachineTag); !ok { return nil, errors.New("manifold can only be used with a machine agent") } w := &stateConfigWatcher{ agent: a, agentConfigChanged: config.AgentConfigChanged, } go func() { defer w.tomb.Done() w.tomb.Kill(w.loop()) }() return w, nil }, Output: outputFunc, } }
// startFunc returns a StartFunc that creates a worker based on the manifolds // named in the supplied config. func startFunc(config ManifoldConfig) dependency.StartFunc { return func(getResource dependency.GetResourceFunc) (worker.Worker, error) { // Get dependencies and open a connection. var a agent.Agent if err := getResource(config.AgentName, &a); err != nil { return nil, err } conn, err := openConnection(a) if err != nil { return nil, errors.Annotate(err, "cannot open api") } // Add the environment uuid to agent config if not present. currentConfig := a.CurrentConfig() if currentConfig.Environment().Id() == "" { err := a.ChangeConfig(func(setter agent.ConfigSetter) error { environTag, err := conn.EnvironTag() if err != nil { return errors.Annotate(err, "no environment uuid set on api") } return setter.Migrate(agent.MigrateParams{ Environment: environTag, }) }) if err != nil { logger.Warningf("unable to save environment uuid: %v", err) // Not really fatal, just annoying. } } // Return the worker. return newApiConnWorker(conn) } }
func newWorker(a agent.Agent, apiCaller base.APICaller) (worker.Worker, error) { w, err := NewWorker(keyupdater.NewState(apiCaller), a.CurrentConfig()) if err != nil { return nil, errors.Annotate(err, "cannot start ssh auth-keys updater worker") } return w, nil }
// Manifold returns a dependency manifold that runs an upgrader // worker, using the resource names defined in the supplied config. func Manifold(config ManifoldConfig) dependency.Manifold { inputs := []string{ config.AgentName, config.APICallerName, } // The machine agent uses these but the unit agent doesn't. if config.UpgradeStepsGateName != "" { inputs = append(inputs, config.UpgradeStepsGateName) } if config.UpgradeCheckGateName != "" { inputs = append(inputs, config.UpgradeCheckGateName) } return dependency.Manifold{ Inputs: inputs, Start: func(context dependency.Context) (worker.Worker, error) { var agent agent.Agent if err := context.Get(config.AgentName, &agent); err != nil { return nil, err } currentConfig := agent.CurrentConfig() var apiCaller base.APICaller if err := context.Get(config.APICallerName, &apiCaller); err != nil { return nil, err } upgraderFacade := upgrader.NewState(apiCaller) var upgradeStepsWaiter gate.Waiter if config.UpgradeStepsGateName == "" { upgradeStepsWaiter = gate.NewLock() } else { if config.PreviousAgentVersion == version.Zero { return nil, errors.New("previous agent version not specified") } if err := context.Get(config.UpgradeStepsGateName, &upgradeStepsWaiter); err != nil { return nil, err } } var initialCheckUnlocker gate.Unlocker if config.UpgradeCheckGateName == "" { initialCheckUnlocker = gate.NewLock() } else { if err := context.Get(config.UpgradeCheckGateName, &initialCheckUnlocker); err != nil { return nil, err } } return NewAgentUpgrader( upgraderFacade, currentConfig, config.PreviousAgentVersion, upgradeStepsWaiter, initialCheckUnlocker, ) }, } }
func (config MachineManifoldConfig) newWorker(a agent.Agent, apiCaller base.APICaller) (worker.Worker, error) { if config.Clock == nil { return nil, dependency.ErrMissing } cfg := a.CurrentConfig() api, err := storageprovisioner.NewState(apiCaller, cfg.Tag()) if err != nil { return nil, errors.Trace(err) } tag, ok := cfg.Tag().(names.MachineTag) if !ok { return nil, errors.Errorf("this manifold may only be used inside a machine agent") } storageDir := filepath.Join(cfg.DataDir(), "storage") w, err := NewStorageProvisioner(Config{ Scope: tag, StorageDir: storageDir, Volumes: api, Filesystems: api, Life: api, Registry: provider.CommonStorageProviders(), Machines: api, Status: api, Clock: config.Clock, }) if err != nil { return nil, errors.Trace(err) } return w, nil }
// newWorker creates a degenerate worker that provides access to an fslock. func newWorker(a agent.Agent) (worker.Worker, error) { dataDir := a.CurrentConfig().DataDir() lock, err := createLock(dataDir) if err != nil { return nil, errors.Trace(err) } return util.NewValueWorker(lock) }
// Manifold returns a dependency manifold that runs a uniter worker, // using the resource names defined in the supplied config. func Manifold(config ManifoldConfig) dependency.Manifold { return dependency.Manifold{ Inputs: []string{ config.AgentName, config.APICallerName, config.LeadershipTrackerName, config.MachineLockName, config.CharmDirName, }, Start: func(getResource dependency.GetResourceFunc) (worker.Worker, error) { // Collect all required resources. var agent agent.Agent if err := getResource(config.AgentName, &agent); err != nil { return nil, err } var apiCaller base.APICaller if err := getResource(config.APICallerName, &apiCaller); err != nil { // TODO(fwereade): absence of an APICaller shouldn't be the end of // the world -- we ought to return a type that can at least run the // leader-deposed hook -- but that's not done yet. return nil, err } var machineLock *fslock.Lock if err := getResource(config.MachineLockName, &machineLock); err != nil { return nil, err } var leadershipTracker leadership.Tracker if err := getResource(config.LeadershipTrackerName, &leadershipTracker); err != nil { return nil, err } var charmDirGuard fortress.Guard if err := getResource(config.CharmDirName, &charmDirGuard); err != nil { return nil, err } // Configure and start the uniter. config := agent.CurrentConfig() tag := config.Tag() unitTag, ok := tag.(names.UnitTag) if !ok { return nil, errors.Errorf("expected a unit tag, got %v", tag) } uniterFacade := uniter.NewState(apiCaller, unitTag) return NewUniter(&UniterParams{ UniterFacade: uniterFacade, UnitTag: unitTag, LeadershipTracker: leadershipTracker, DataDir: config.DataDir(), MachineLock: machineLock, CharmDirGuard: charmDirGuard, UpdateStatusSignal: NewUpdateStatusTimer(), NewOperationExecutor: operation.NewExecutor, Clock: clock.WallClock, }), nil }, } }
func getAPIAddresses(a agent.Agent) []string { config := a.CurrentConfig() addrs, err := config.APIAddresses() if err != nil { logger.Errorf("retrieving API addresses: %s", err) addrs = nil } sort.Strings(addrs) return addrs }
// Manifold returns a manifold whose worker which wraps a // *state.State, which is in turn wrapper by a StateTracker. It will // exit if the State's associated mongodb session dies. func Manifold(config ManifoldConfig) dependency.Manifold { return dependency.Manifold{ Inputs: []string{ config.AgentName, config.StateConfigWatcherName, }, Start: func(context dependency.Context) (worker.Worker, error) { // First, a sanity check. if config.OpenState == nil { return nil, errors.New("OpenState is nil in config") } // Get the agent. var agent coreagent.Agent if err := context.Get(config.AgentName, &agent); err != nil { return nil, err } // Confirm we're running in a state server by asking the // stateconfigwatcher manifold. var haveStateConfig bool if err := context.Get(config.StateConfigWatcherName, &haveStateConfig); err != nil { return nil, err } if !haveStateConfig { return nil, dependency.ErrMissing } st, err := config.OpenState(agent.CurrentConfig()) if err != nil { return nil, errors.Trace(err) } stTracker := newStateTracker(st) pingInterval := config.PingInterval if pingInterval == 0 { pingInterval = defaultPingInterval } w := &stateWorker{ stTracker: stTracker, pingInterval: pingInterval, } go func() { defer w.tomb.Done() w.tomb.Kill(w.loop()) if err := stTracker.Done(); err != nil { logger.Errorf("error releasing state: %v", err) } }() return w, nil }, Output: outputFunc, } }
// newWorker trivially wraps NewWorker for use in a engine.AgentApiManifold. func newWorker(a agent.Agent, apiCaller base.APICaller) (worker.Worker, error) { t := a.CurrentConfig().Tag() tag, ok := t.(names.MachineTag) if !ok { return nil, errors.Errorf("expected MachineTag, got %#v", t) } api := apidiskmanager.NewState(apiCaller, tag) return NewWorker(DefaultListBlockDevices, api), nil }
// uninstallerManifold defines a simple start function which retrieves // some dependencies, checks if the machine is dead and causes the // agent to uninstall itself if it is. This doubles up on part of the // machiner's functionality but the machiner doesn't run until // upgrades are complete, and the upgrade related workers may not be // able to make API requests if the machine is dead. func uninstallerManifold(config uninstallerManifoldConfig) dependency.Manifold { return dependency.Manifold{ Inputs: []string{ config.AgentName, config.APICallerName, }, Start: func(getResource dependency.GetResourceFunc) (worker.Worker, error) { if config.WriteUninstallFile == nil { return nil, errors.New("WriteUninstallFile not specified") } // Get the agent. var agent agent.Agent if err := getResource(config.AgentName, &agent); err != nil { return nil, err } // Grab the tag and ensure that it's for a machine. tag, ok := agent.CurrentConfig().Tag().(names.MachineTag) if !ok { return nil, errors.New("agent's tag is not a machine tag") } // Get API connection. // // TODO(mjs) - this should really be a base.APICaller to // remove the possibility of the API connection being closed // here. var apiConn api.Connection if err := getResource(config.APICallerName, &apiConn); err != nil { return nil, err } // Check if the machine is dead and set the agent to // uninstall if it is. // // TODO(mjs) - ideally this would be using its own facade. machine, err := apiConn.Agent().Entity(tag) if err != nil { return nil, err } if machine.Life() == params.Dead { if err := config.WriteUninstallFile(); err != nil { return nil, errors.Annotate(err, "writing uninstall agent file") } return nil, worker.ErrTerminateAgent } // All is well - we're done (no actual worker is actually returned). return nil, dependency.ErrUninstall }, } }
// start is used by util.AgentApiManifold to create a StartFunc. func (config ManifoldConfig) start(a agent.Agent, apiCaller base.APICaller) (worker.Worker, error) { machineTag, ok := a.CurrentConfig().Tag().(names.MachineTag) if !ok { return nil, errors.Errorf("this manifold can only be used inside a machine") } machineActionsFacade := config.NewFacade(apiCaller) return config.NewWorker(WorkerConfig{ Facade: machineActionsFacade, MachineTag: machineTag, HandleAction: HandleAction, }) }
// ServingInfoSetterManifold defines a simple start function which // runs after the API connection has come up. If the machine agent is // a controller, it grabs the state serving info over the API and // records it to agent configuration, and then stops. func ServingInfoSetterManifold(config ServingInfoSetterConfig) dependency.Manifold { return dependency.Manifold{ Inputs: []string{ config.AgentName, config.APICallerName, }, Start: func(context dependency.Context) (worker.Worker, error) { // Get the agent. var agent coreagent.Agent if err := context.Get(config.AgentName, &agent); err != nil { return nil, err } // Grab the tag and ensure that it's for a machine. tag, ok := agent.CurrentConfig().Tag().(names.MachineTag) if !ok { return nil, errors.New("agent's tag is not a machine tag") } // Get API connection. var apiCaller base.APICaller if err := context.Get(config.APICallerName, &apiCaller); err != nil { return nil, err } apiState := apiagent.NewState(apiCaller) // If the machine needs State, grab the state serving info // over the API and write it to the agent configuration. machine, err := apiState.Entity(tag) if err != nil { return nil, err } for _, job := range machine.Jobs() { if job.NeedsState() { info, err := apiState.StateServingInfo() if err != nil { return nil, errors.Errorf("cannot get state serving info: %v", err) } err = agent.ChangeConfig(func(config coreagent.ConfigSetter) error { config.SetStateServingInfo(info) return nil }) if err != nil { return nil, err } } } // All is well - we're done (no actual worker is actually returned). return nil, dependency.ErrUninstall }, } }
// OnlyConnect logs into the API using the supplied agent's credentials. func OnlyConnect(a agent.Agent, apiOpen api.OpenFunc) (api.Connection, error) { agentConfig := a.CurrentConfig() info, ok := agentConfig.APIInfo() if !ok { return nil, errors.New("API info not available") } conn, _, err := connectFallback(apiOpen, info, agentConfig.OldPassword()) if err != nil { return nil, errors.Trace(err) } return conn, nil }
func newWorker(a agent.Agent, apiCaller base.APICaller) (worker.Worker, error) { apiConn, ok := apiCaller.(api.Connection) if !ok { return nil, errors.New("unable to obtain api.Connection") } w, err := NewWorker(keyupdater.NewState(apiConn), a.CurrentConfig()) if err != nil { return nil, errors.Annotate(err, "cannot start ssh auth-keys updater worker") } return w, nil }
// isModelManager returns whether the agent has JobManageModel, // or an error. func isModelManager(a agent.Agent, apiCaller base.APICaller) (bool, error) { agentFacade := apiagent.NewState(apiCaller) entity, err := agentFacade.Entity(a.CurrentConfig().Tag()) if err != nil { return false, errors.Trace(err) } for _, job := range entity.Jobs() { if job == multiwatcher.JobManageModel { return true, nil } } return false, nil }
func (mc ManifoldConfig) start(a agent.Agent, apiCaller base.APICaller) (worker.Worker, error) { agentTag := a.CurrentConfig().Tag() retryStrategyFacade := mc.NewFacade(apiCaller) initialRetryStrategy, err := retryStrategyFacade.RetryStrategy(agentTag) if err != nil { return nil, errors.Trace(err) } return mc.NewWorker(WorkerConfig{ Facade: retryStrategyFacade, AgentTag: agentTag, RetryStrategy: initialRetryStrategy, }) }
// newWorker is not currently tested; it should eventually replace New as the // package's exposed factory func, and then all tests should pass through it. func newWorker(a agent.Agent, apiCaller base.APICaller) (worker.Worker, error) { agentConfig := a.CurrentConfig() switch tag := agentConfig.Tag().(type) { case names.MachineTag, names.UnitTag: default: return nil, errors.Errorf("unknown agent type: %T", tag) } // TODO(fwereade): This shouldn't be an "environment" facade, it // should be specific to the proxyupdater, and be watching for // *proxy settings* changes, not just watching the "environment". return NewWorker(proxyupdater.NewFacade(apiCaller)) }
// newWorker creates a degenerate worker that provides access to the metrics // spool directory path. func newWorker(a agent.Agent) (worker.Worker, error) { metricsSpoolDir := a.CurrentConfig().MetricsSpoolDir() err := checkSpoolDir(metricsSpoolDir) if err != nil { return nil, errors.Annotatef(err, "error checking spool directory %q", metricsSpoolDir) } w := &spoolWorker{factory: newFactory(metricsSpoolDir)} go func() { defer w.tomb.Done() <-w.tomb.Dying() }() return w, nil }
func newStatusWorker(config ManifoldConfig, getResource dependency.GetResourceFunc) (worker.Worker, error) { var agent agent.Agent if err := getResource(config.AgentName, &agent); err != nil { return nil, err } var machineLock *fslock.Lock if err := getResource(config.MachineLockName, &machineLock); err != nil { return nil, err } tag := agent.CurrentConfig().Tag() unitTag, ok := tag.(names.UnitTag) if !ok { return nil, errors.Errorf("expected unit tag, got %v", tag) } agentConfig := agent.CurrentConfig() stateFile := NewStateFile(path.Join(agentConfig.DataDir(), "meter-status.yaml")) runner := config.NewHookRunner(unitTag, machineLock, agentConfig) // If we don't have a valid APICaller, start a meter status // worker that works without an API connection. var apiCaller base.APICaller err := getResource(config.APICallerName, &apiCaller) if errors.Cause(err) == dependency.ErrMissing { logger.Tracef("API caller dependency not available, starting isolated meter status worker.") cfg := IsolatedConfig{ Runner: runner, StateFile: stateFile, Clock: clock.WallClock, AmberGracePeriod: defaultAmberGracePeriod, RedGracePeriod: defaultRedGracePeriod, TriggerFactory: GetTriggers, } return config.NewIsolatedStatusWorker(cfg) } else if err != nil { return nil, err } logger.Tracef("Starting connected meter status worker.") status := config.NewMeterStatusAPIClient(apiCaller, unitTag) cfg := ConnectedConfig{ Runner: runner, StateFile: stateFile, Status: status, } return config.NewConnectedStatusWorker(cfg) }
func setAgentPassword(newPw, oldPw string, a agent.Agent, entity *apiagent.Entity) error { // Change the configuration *before* setting the entity // password, so that we avoid the possibility that // we might successfully change the entity's // password but fail to write the configuration, // thus locking us out completely. if err := a.ChangeConfig(func(c agent.ConfigSetter) error { c.SetPassword(newPw) c.SetOldPassword(oldPw) return nil }); err != nil { return err } return entity.SetPassword(newPw) }
// NewWorker returns a new instance of the upgradesteps worker. It // will run any required steps to upgrade to the currently running // Juju version. func NewWorker( upgradeComplete gate.Lock, agent agent.Agent, apiConn api.Connection, jobs []multiwatcher.MachineJob, openState func() (*state.State, error), preUpgradeSteps func(st *state.State, agentConf agent.Config, isController, isMasterServer bool) error, machine StatusSetter, ) (worker.Worker, error) { tag, ok := agent.CurrentConfig().Tag().(names.MachineTag) if !ok { return nil, errors.New("machine agent's tag is not a MachineTag") } w := &upgradesteps{ upgradeComplete: upgradeComplete, agent: agent, apiConn: apiConn, jobs: jobs, openState: openState, preUpgradeSteps: preUpgradeSteps, machine: machine, tag: tag, } go func() { defer w.tomb.Done() w.tomb.Kill(w.run()) }() return w, nil }
// changePassword generates a new random password and records it in // local agent configuration and on the remote state server. The supplied // oldPassword -- which must be the current valid password -- is set as a // fallback in local config, in case we fail to update the remote password. func changePassword(oldPassword string, a agent.Agent, facade apiagent.ConnFacade) error { newPassword, err := utils.RandomPassword() if err != nil { return errors.Trace(err) } if err := a.ChangeConfig(func(c agent.ConfigSetter) error { c.SetPassword(newPassword) c.SetOldPassword(oldPassword) return nil }); err != nil { return err } // This has to happen *after* we record the old/new passwords // locally, lest we change it remotely, crash suddenly, and // end up locked out forever. return facade.SetPassword(a.CurrentConfig().Tag(), newPassword) }
// maybeSetAgentModelTag tries to update the agent configuration if // it's missing a model tag. It doesn't *really* matter if it fails, // because we can demonstrably connect without it, so we log any // errors encountered and never return any to the client. func maybeSetAgentModelTag(a agent.Agent, conn api.Connection) { if a.CurrentConfig().Model().Id() == "" { err := a.ChangeConfig(func(setter agent.ConfigSetter) error { modelTag, err := conn.ModelTag() if err != nil { return errors.Annotate(err, "no model uuid set on api") } return setter.Migrate(agent.MigrateParams{ Model: modelTag, }) }) if err != nil { logger.Warningf("unable to save model uuid: %v", err) // Not really fatal, just annoying. } } }
// newWorker trivially wraps NewReboot for use in a util.PostUpgradeManifold. // // TODO(mjs) - It's not tested at the moment, because the scaffolding // necessary is too unwieldy/distracting to introduce at this point. func newWorker(a agent.Agent, apiCaller base.APICaller) (worker.Worker, error) { apiConn, ok := apiCaller.(api.Connection) if !ok { return nil, errors.New("unable to obtain api.Connection") } rebootState, err := apiConn.Reboot() if err != nil { return nil, errors.Trace(err) } lock, err := cmdutil.HookExecutionLock(cmdutil.DataDir) if err != nil { return nil, errors.Trace(err) } w, err := NewReboot(rebootState, a.CurrentConfig(), lock) if err != nil { return nil, errors.Annotate(err, "cannot start reboot worker") } return w, nil }
// newWorker is not currently tested; it should eventually replace New as the // package's exposed factory func, and then all tests should pass through it. func newWorker(a agent.Agent, apiCaller base.APICaller) (worker.Worker, error) { agentConfig := a.CurrentConfig() switch tag := agentConfig.Tag().(type) { case names.MachineTag, names.UnitTag: default: return nil, errors.Errorf("unknown agent type: %T", tag) } proxyAPI, err := proxyupdater.NewAPI(apiCaller, agentConfig.Tag()) if err != nil { return nil, err } return NewWorker(Config{ Directory: "/home/ubuntu", RegistryPath: `HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings`, Filename: ".juju-proxy", API: proxyAPI, }) }