github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/caasoperator/manifold.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package caasoperator 5 6 import ( 7 "crypto/tls" 8 "encoding/pem" 9 "os" 10 "path" 11 "time" 12 13 "github.com/juju/clock" 14 "github.com/juju/errors" 15 "github.com/juju/http/v2" 16 "github.com/juju/loggo" 17 "github.com/juju/names/v5" 18 "github.com/juju/worker/v3" 19 "github.com/juju/worker/v3/dependency" 20 21 "github.com/juju/juju/agent" 22 apileadership "github.com/juju/juju/api/agent/leadership" 23 "github.com/juju/juju/api/agent/secretsmanager" 24 apiuniter "github.com/juju/juju/api/agent/uniter" 25 "github.com/juju/juju/api/base" 26 "github.com/juju/juju/caas" 27 caasconstants "github.com/juju/juju/caas/kubernetes/provider/constants" 28 "github.com/juju/juju/caas/kubernetes/provider/exec" 29 coreleadership "github.com/juju/juju/core/leadership" 30 "github.com/juju/juju/core/machinelock" 31 "github.com/juju/juju/juju/sockets" 32 "github.com/juju/juju/rpc/params" 33 "github.com/juju/juju/secrets" 34 "github.com/juju/juju/worker/fortress" 35 "github.com/juju/juju/worker/leadership" 36 "github.com/juju/juju/worker/secretexpire" 37 "github.com/juju/juju/worker/secretrotate" 38 "github.com/juju/juju/worker/uniter" 39 "github.com/juju/juju/worker/uniter/charm" 40 "github.com/juju/juju/worker/uniter/operation" 41 "github.com/juju/juju/worker/uniter/runner" 42 ) 43 44 type Logger interface { 45 Debugf(string, ...interface{}) 46 Infof(string, ...interface{}) 47 Errorf(string, ...interface{}) 48 Warningf(string, ...interface{}) 49 50 Child(string) loggo.Logger 51 } 52 53 // ManifoldConfig defines the names of the manifolds on which a 54 // Manifold will depend. 55 type ManifoldConfig struct { 56 Logger Logger 57 58 AgentName string 59 APICallerName string 60 ClockName string 61 62 MachineLock machinelock.Lock 63 LeadershipGuarantee time.Duration 64 CharmDirName string 65 ProfileDir string 66 HookRetryStrategyName string 67 TranslateResolverErr func(error) error 68 69 NewWorker func(Config) (worker.Worker, error) 70 NewClient func(base.APICaller) Client 71 NewCharmDownloader func(base.APICaller) Downloader 72 73 NewExecClient func(namespace string) (exec.Executor, error) 74 RunListenerSocket func(*uniter.SocketConfig) (*sockets.Socket, error) 75 76 LoadOperatorInfo func(paths Paths) (*caas.OperatorInfo, error) 77 78 NewContainerStartWatcherClient func(Client) ContainerStartWatcher 79 } 80 81 func (config ManifoldConfig) Validate() error { 82 if config.Logger == nil { 83 return errors.NotValidf("missing Logger") 84 } 85 if config.AgentName == "" { 86 return errors.NotValidf("empty AgentName") 87 } 88 if config.APICallerName == "" { 89 return errors.NotValidf("empty APICallerName") 90 } 91 if config.ClockName == "" { 92 return errors.NotValidf("empty ClockName") 93 } 94 if config.NewWorker == nil { 95 return errors.NotValidf("missing NewWorker") 96 } 97 if config.NewClient == nil { 98 return errors.NotValidf("missing NewClient") 99 } 100 if config.NewCharmDownloader == nil { 101 return errors.NotValidf("missing NewCharmDownloader") 102 } 103 if config.CharmDirName == "" { 104 return errors.NotValidf("missing CharmDirName") 105 } 106 if config.ProfileDir == "" { 107 return errors.NotValidf("missing ProfileDir") 108 } 109 if config.MachineLock == nil { 110 return errors.NotValidf("missing MachineLock") 111 } 112 if config.HookRetryStrategyName == "" { 113 return errors.NotValidf("missing HookRetryStrategyName") 114 } 115 if config.LeadershipGuarantee == 0 { 116 return errors.NotValidf("missing LeadershipGuarantee") 117 } 118 if config.NewExecClient == nil { 119 return errors.NotValidf("missing NewExecClient") 120 } 121 return nil 122 } 123 124 // Manifold returns a dependency manifold that runs a caasoperator worker, 125 // using the resource names defined in the supplied config. 126 func Manifold(config ManifoldConfig) dependency.Manifold { 127 return dependency.Manifold{ 128 Inputs: []string{ 129 config.AgentName, 130 config.APICallerName, 131 config.ClockName, 132 config.CharmDirName, 133 config.HookRetryStrategyName, 134 }, 135 Start: func(context dependency.Context) (worker.Worker, error) { 136 if err := config.Validate(); err != nil { 137 return nil, errors.Trace(err) 138 } 139 140 var agent agent.Agent 141 if err := context.Get(config.AgentName, &agent); err != nil { 142 return nil, errors.Trace(err) 143 } 144 145 var apiCaller base.APICaller 146 if err := context.Get(config.APICallerName, &apiCaller); err != nil { 147 return nil, errors.Trace(err) 148 } 149 client := config.NewClient(apiCaller) 150 downloader := config.NewCharmDownloader(apiCaller) 151 152 var clock clock.Clock 153 if err := context.Get(config.ClockName, &clock); err != nil { 154 return nil, errors.Trace(err) 155 } 156 157 model, err := client.Model() 158 if err != nil { 159 return nil, errors.Trace(err) 160 } 161 162 var charmDirGuard fortress.Guard 163 if err := context.Get(config.CharmDirName, &charmDirGuard); err != nil { 164 return nil, err 165 } 166 167 var hookRetryStrategy params.RetryStrategy 168 if err := context.Get(config.HookRetryStrategyName, &hookRetryStrategy); err != nil { 169 return nil, err 170 } 171 172 // Configure and start the caasoperator worker. 173 agentConfig := agent.CurrentConfig() 174 tag := agentConfig.Tag() 175 applicationTag, ok := tag.(names.ApplicationTag) 176 if !ok { 177 return nil, errors.Errorf("expected an application tag, got %v", tag) 178 } 179 newUniterFunc := func(unitTag names.UnitTag) *apiuniter.State { 180 return apiuniter.NewState(apiCaller, unitTag) 181 } 182 newResourcesFacadeFunc := func(unitTag names.UnitTag) (*apiuniter.ResourcesFacadeClient, error) { 183 return apiuniter.NewResourcesFacadeClient(apiCaller, unitTag) 184 } 185 newPayloadFacadeFunc := func() *apiuniter.PayloadFacadeClient { 186 return apiuniter.NewPayloadFacadeClient(apiCaller) 187 } 188 leadershipTrackerFunc := func(unitTag names.UnitTag) coreleadership.TrackerWorker { 189 claimer := apileadership.NewClient(apiCaller) 190 return leadership.NewTracker(unitTag, claimer, clock, config.LeadershipGuarantee) 191 } 192 193 runListenerSocketFunc := config.RunListenerSocket 194 if runListenerSocketFunc == nil { 195 runListenerSocketFunc = runListenerSocket 196 } 197 containerStartWatcherClient := config.NewContainerStartWatcherClient 198 if containerStartWatcherClient == nil { 199 containerStartWatcherClient = func(c Client) ContainerStartWatcher { 200 return c 201 } 202 } 203 jujuSecretsAPI := secretsmanager.NewClient(apiCaller) 204 secretsBackendGetter := func() (secrets.BackendsClient, error) { 205 return secrets.NewClient(jujuSecretsAPI) 206 } 207 secretRotateWatcherFunc := func(unitTag names.UnitTag, isLeader bool, rotateSecrets chan []string) (worker.Worker, error) { 208 owners := []names.Tag{unitTag} 209 if isLeader { 210 appName, _ := names.UnitApplication(unitTag.Id()) 211 owners = append(owners, names.NewApplicationTag(appName)) 212 } 213 return secretrotate.New(secretrotate.Config{ 214 SecretManagerFacade: jujuSecretsAPI, 215 Clock: clock, 216 Logger: config.Logger.Child("secretsrotate"), 217 SecretOwners: owners, 218 RotateSecrets: rotateSecrets, 219 }) 220 } 221 secretExpiryWatcherFunc := func(unitTag names.UnitTag, isLeader bool, expireRevisions chan []string) (worker.Worker, error) { 222 owners := []names.Tag{unitTag} 223 if isLeader { 224 appName, _ := names.UnitApplication(unitTag.Id()) 225 owners = append(owners, names.NewApplicationTag(appName)) 226 } 227 return secretexpire.New(secretexpire.Config{ 228 SecretManagerFacade: jujuSecretsAPI, 229 Clock: clock, 230 Logger: config.Logger.Child("secretsexpire"), 231 SecretOwners: owners, 232 ExpireRevisions: expireRevisions, 233 }) 234 } 235 236 wCfg := Config{ 237 Logger: config.Logger, 238 ModelUUID: agentConfig.Model().Id(), 239 ModelName: model.Name, 240 Application: applicationTag.Id(), 241 CharmGetter: client, 242 Clock: clock, 243 DataDir: agentConfig.DataDir(), 244 ProfileDir: config.ProfileDir, 245 Downloader: downloader, 246 StatusSetter: client, 247 UnitGetter: client, 248 UnitRemover: client, 249 ApplicationWatcher: client, 250 ContainerStartWatcher: containerStartWatcherClient(client), 251 VersionSetter: client, 252 StartUniterFunc: uniter.StartUniter, 253 RunListenerSocketFunc: runListenerSocketFunc, 254 LeadershipTrackerFunc: leadershipTrackerFunc, 255 UniterFacadeFunc: newUniterFunc, 256 ResourcesFacadeFunc: newResourcesFacadeFunc, 257 PayloadFacadeFunc: newPayloadFacadeFunc, 258 ExecClientGetter: func() (exec.Executor, error) { 259 return config.NewExecClient(os.Getenv(caasconstants.OperatorNamespaceEnvName)) 260 }, 261 } 262 263 loadOperatorInfoFunc := config.LoadOperatorInfo 264 if loadOperatorInfoFunc == nil { 265 loadOperatorInfoFunc = LoadOperatorInfo 266 } 267 operatorInfo, err := loadOperatorInfoFunc(wCfg.getPaths()) 268 if err != nil { 269 return nil, errors.Trace(err) 270 } 271 wCfg.OperatorInfo = *operatorInfo 272 wCfg.UniterParams = &uniter.UniterParams{ 273 NewOperationExecutor: operation.NewExecutor, 274 NewDeployer: charm.NewDeployer, 275 NewProcessRunner: runner.NewRunner, 276 DataDir: agentConfig.DataDir(), 277 Clock: clock, 278 MachineLock: config.MachineLock, 279 CharmDirGuard: charmDirGuard, 280 UpdateStatusSignal: uniter.NewUpdateStatusTimer(), 281 HookRetryStrategy: hookRetryStrategy, 282 TranslateResolverErr: config.TranslateResolverErr, 283 SecretsClient: jujuSecretsAPI, 284 SecretsBackendGetter: secretsBackendGetter, 285 SecretRotateWatcherFunc: secretRotateWatcherFunc, 286 SecretExpiryWatcherFunc: secretExpiryWatcherFunc, 287 Logger: wCfg.Logger.Child("uniter"), 288 } 289 wCfg.UniterParams.SocketConfig, err = socketConfig(operatorInfo) 290 if err != nil { 291 return nil, errors.Trace(err) 292 } 293 294 w, err := config.NewWorker(wCfg) 295 if err != nil { 296 return nil, errors.Trace(err) 297 } 298 return w, nil 299 }, 300 } 301 } 302 303 func socketConfig(info *caas.OperatorInfo) (*uniter.SocketConfig, error) { 304 tlsCert, err := tls.X509KeyPair([]byte(info.Cert), []byte(info.PrivateKey)) 305 if err != nil { 306 return nil, errors.Annotatef(err, "cannot parse operator TLS certificate") 307 } 308 309 block, _ := pem.Decode([]byte(info.CACert)) 310 tlsCert.Certificate = append(tlsCert.Certificate, block.Bytes) 311 tlsConfig := http.SecureTLSConfig() 312 tlsConfig.Certificates = []tls.Certificate{tlsCert} 313 314 serviceAddress := os.Getenv(caasconstants.OperatorServiceIPEnvName) 315 if serviceAddress == "" { 316 return nil, errors.Errorf("env %s missing", caasconstants.OperatorServiceIPEnvName) 317 } 318 319 operatorAddress := os.Getenv(caasconstants.OperatorPodIPEnvName) 320 if operatorAddress == "" { 321 return nil, errors.Errorf("env %s missing", caasconstants.OperatorPodIPEnvName) 322 } 323 324 sc := &uniter.SocketConfig{ 325 ServiceAddress: serviceAddress, 326 OperatorAddress: operatorAddress, 327 TLSConfig: tlsConfig, 328 } 329 return sc, nil 330 } 331 332 // LoadOperatorInfo loads the operator info file from the state dir. 333 func LoadOperatorInfo(paths Paths) (*caas.OperatorInfo, error) { 334 filepath := path.Join(paths.State.BaseDir, caas.OperatorInfoFile) 335 data, err := os.ReadFile(filepath) 336 if err != nil { 337 return nil, errors.Annotatef(err, "reading operator info file %s", filepath) 338 } 339 return caas.UnmarshalOperatorInfo(data) 340 }