LoadModuleByID decodes rawMsg into a new instance of mod and returns the value. If mod.New is nil, an error is returned. If the module implements Validator or Provisioner interfaces, those methods are invoked to ensure the module is fully configured and valid before being used. This is a lower-leve
(id string, rawMsg json.RawMessage)
| 362 | // dynamically loading/unloading modules in their own context, |
| 363 | // like from embedded scripts, etc. |
| 364 | func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error) { |
| 365 | modulesMu.RLock() |
| 366 | modInfo, ok := modules[id] |
| 367 | modulesMu.RUnlock() |
| 368 | if !ok { |
| 369 | return nil, fmt.Errorf("unknown module: %s", id) |
| 370 | } |
| 371 | |
| 372 | if modInfo.New == nil { |
| 373 | return nil, fmt.Errorf("module '%s' has no constructor", modInfo.ID) |
| 374 | } |
| 375 | |
| 376 | val := modInfo.New() |
| 377 | |
| 378 | // value must be a pointer for unmarshaling into concrete type, even if |
| 379 | // the module's concrete type is a slice or map; New() *should* return |
| 380 | // a pointer, otherwise unmarshaling errors or panics will occur |
| 381 | if rv := reflect.ValueOf(val); rv.Kind() != reflect.Pointer { |
| 382 | log.Printf("[WARNING] ModuleInfo.New() for module '%s' did not return a pointer,"+ |
| 383 | " so we are using reflection to make a pointer instead; please fix this by"+ |
| 384 | " using new(Type) or &Type notation in your module's New() function.", id) |
| 385 | val = reflect.New(rv.Type()).Elem().Addr().Interface().(Module) |
| 386 | } |
| 387 | |
| 388 | // fill in its config only if there is a config to fill in |
| 389 | if len(rawMsg) > 0 { |
| 390 | err := StrictUnmarshalJSON(rawMsg, &val) |
| 391 | if err != nil { |
| 392 | return nil, fmt.Errorf("decoding module config: %s: %v", modInfo, err) |
| 393 | } |
| 394 | } |
| 395 | |
| 396 | if val == nil { |
| 397 | // returned module values are almost always type-asserted |
| 398 | // before being used, so a nil value would panic; and there |
| 399 | // is no good reason to explicitly declare null modules in |
| 400 | // a config; it might be because the user is trying to achieve |
| 401 | // a result the developer isn't expecting, which is a smell |
| 402 | return nil, fmt.Errorf("module value cannot be null") |
| 403 | } |
| 404 | |
| 405 | var err error |
| 406 | |
| 407 | // if this is an app module, keep a reference to it, |
| 408 | // since submodules may need to reference it during |
| 409 | // provisioning (even though the parent app module |
| 410 | // may not be fully provisioned yet; this is the case |
| 411 | // with the tls app's automation policies, which may |
| 412 | // refer to the tls app to check if a global DNS |
| 413 | // module has been configured for DNS challenges) |
| 414 | if appModule, ok := val.(App); ok { |
| 415 | ctx.cfg.apps[id] = appModule |
| 416 | defer func() { |
| 417 | if err != nil { |
| 418 | ctx.cfg.failedApps[id] = err |
| 419 | } |
| 420 | }() |
| 421 | } |