App not available during provisioning

I am creating a custom middleware which has a dependency on a custom app.
There are times the app is shown as not configured what starting up caddy.
How can I enforce the app is created before provisioning the middleware?
Or what work arounds can do?

Caddyfile

{
   app_name config
}

localhost {
  route {
    middleware
  }
}

During Provisioning the middleware
I have the following check.

   app := ctx.AppIfConfigured(“app_name”)
   if app == nil {
      return errors.New("The app_name is not configured")
   }

If you need to ensure the App exists, use App(), not AppIfConfigured(). See if that helps you out.

1 Like

Thank you Matt.
I will try that and let you know.

That worked thank you.
The issue seems to some the fact the order of loading apps seems to be random.

Would you want me to create an issue for this or it is fine?

This is by design. App provisioning order is not guaranteed, but they can be loaded on-demand if they’re depended on.

2 Likes

Yep. The point of AppIfConfigured() is to only provision an app if one was explicitly configured by the user, so another module can choose to “do nothing” or “choose a default” if no config exists. If you expect config to actually exist, then App() is the correct thing to use. The Godoc comment for these methods explain this.

2 Likes

It seems that AppIfConfigured can fail in a module’s provisioning process even if the app configured.
For now I am using the my App’s default state as an error state.

I see, so basically you’re looking for a way to tell apart whether an app was provisioned from no config (default), or actually user-configured. I think that is an ambiguous case with the current API. We should improve that.

1 Like

If you want I can do a pull request on this or you can just take it.

diff --git a/context.go b/context.go
index 4307dda8..7af75d39 100644
--- a/context.go
+++ b/context.go
@@ -469,7 +469,25 @@ func (ctx Context) AppIfConfigured(name string) any {
                // been loaded yet
                return nil
        }
-       return ctx.cfg.apps[name]
+
+       if app, ok := ctx.cfg.apps[name]; ok {
+               return app
+       }
+
+       appRaw, ok := ctx.cfg.AppsRaw[name]
+       if !ok {
+               return nil
+       }
+
+       modVal, err := ctx.LoadModuleByID(name, appRaw)
+       if err != nil {
+               return nil
+       }
+
+       ctx.cfg.AppsRaw[name] = nil // allow GC to deallocate
+       ctx.cfg.apps[name] = modVal.(App)
+
+       return modVal
 }

 // Storage returns the configured Caddy storage implementation.
1 Like

I don’t think we should change AppIfConfigured, it has a specific purpose. It specifically documents that it will not load a module if not already loaded.

I think we should add a new AppStrict() which works just like App() (and returns an error if any) which returns an error early if the app is not configured.

I’m working on that now.

2 Likes

Ok thank you for your help on this

3 Likes

Part of the confusion is about a year ago AppIfConfigured was changed. Prior to that, it actually would load the app if was configured but not loaded. See caddytls: Reuse certificate cache through reloads (#5623) · caddyserver/caddy@0e2c7e1 · GitHub

2 Likes