From 5859cd8dad32fdd7ea55daa5e4377e273fb97a3e Mon Sep 17 00:00:00 2001
From: Matthew Holt <mholt@users.noreply.github.com>
Date: Mon, 29 Apr 2019 09:22:00 -0600
Subject: [PATCH] Instantiate apps that are needed but not explicitly
 configured

---
 caddy.go                       | 18 +++++++++++++-----
 modules.go                     |  9 ++++++---
 modules/caddyhttp/caddyhttp.go | 30 ++++++++++++++++++++----------
 modules/caddytls/connpolicy.go |  6 +++++-
 4 files changed, 44 insertions(+), 19 deletions(-)

diff --git a/caddy.go b/caddy.go
index 62b12b636..2aa0c6a20 100644
--- a/caddy.go
+++ b/caddy.go
@@ -238,11 +238,19 @@ type Handle struct {
 	current *Config
 }
 
-// App returns the configured app named name.
-// A nil value is returned if no app with that
-// name is currently configured.
-func (h Handle) App(name string) interface{} {
-	return h.current.apps[name]
+// App returns the configured app named name. If no app with
+// that name is currently configured, a new empty one will be
+// instantiated. (The app module must still be plugged in.)
+func (h Handle) App(name string) (interface{}, error) {
+	if app, ok := h.current.apps[name]; ok {
+		return app, nil
+	}
+	modVal, err := LoadModule(name, nil)
+	if err != nil {
+		return nil, fmt.Errorf("instantiating new module %s: %v", name, err)
+	}
+	h.current.apps[name] = modVal.(App)
+	return modVal, nil
 }
 
 // GetStorage returns the configured Caddy storage implementation.
diff --git a/modules.go b/modules.go
index 5a4e69fc0..6afa6fb9f 100644
--- a/modules.go
+++ b/modules.go
@@ -171,9 +171,12 @@ func LoadModule(name string, rawMsg json.RawMessage) (interface{}, error) {
 		val = reflect.New(rv.Type()).Elem().Addr().Interface()
 	}
 
-	err = json.Unmarshal(rawMsg, &val)
-	if err != nil {
-		return nil, fmt.Errorf("decoding module config: %s: %v", mod.Name, err)
+	// fill in its config only if there is a config to fill in
+	if len(rawMsg) > 0 {
+		err = json.Unmarshal(rawMsg, &val)
+		if err != nil {
+			return nil, fmt.Errorf("decoding module config: %s: %v", mod.Name, err)
+		}
 	}
 
 	if prov, ok := val.(Provisioner); ok {
diff --git a/modules/caddyhttp/caddyhttp.go b/modules/caddyhttp/caddyhttp.go
index 437e48f77..de62b7971 100644
--- a/modules/caddyhttp/caddyhttp.go
+++ b/modules/caddyhttp/caddyhttp.go
@@ -111,7 +111,11 @@ func (hc *httpModuleConfig) Stop() error {
 }
 
 func (hc *httpModuleConfig) automaticHTTPS(handle caddy2.Handle) error {
-	tlsApp := handle.App("tls").(*caddytls.TLS)
+	tlsAppIface, err := handle.App("tls")
+	if err != nil {
+		return fmt.Errorf("getting tls app: %v", err)
+	}
+	tlsApp := tlsAppIface.(*caddytls.TLS)
 
 	for srvName, srv := range hc.Servers {
 		srv.tlsApp = tlsApp
@@ -120,6 +124,7 @@ func (hc *httpModuleConfig) automaticHTTPS(handle caddy2.Handle) error {
 			continue
 		}
 
+		// find all qualifying domain names, de-duplicated
 		domainSet := make(map[string]struct{})
 		for _, route := range srv.Routes {
 			for _, m := range route.matchers {
@@ -133,21 +138,26 @@ func (hc *httpModuleConfig) automaticHTTPS(handle caddy2.Handle) error {
 				}
 			}
 		}
-		var domains []string
-		for d := range domainSet {
-			domains = append(domains, d)
-		}
-		if len(domains) > 0 {
+
+		if len(domainSet) > 0 {
+			// marshal the domains into a slice
+			var domains []string
+			for d := range domainSet {
+				domains = append(domains, d)
+			}
+
+			// manage their certificates
 			err := tlsApp.Manage(domains)
 			if err != nil {
 				return fmt.Errorf("%s: managing certificate for %s: %s", srvName, domains, err)
 			}
-			// TODO: Connection policies... redirects... man...
+
+			// tell the server to use TLS
 			srv.TLSConnPolicies = caddytls.ConnectionPolicies{
-				{
-					ALPN: defaultALPN,
-				},
+				{ALPN: defaultALPN},
 			}
+
+			// TODO: create HTTP->HTTPS redirects
 		}
 	}
 
diff --git a/modules/caddytls/connpolicy.go b/modules/caddytls/connpolicy.go
index 94000345f..a085fa30f 100644
--- a/modules/caddytls/connpolicy.go
+++ b/modules/caddytls/connpolicy.go
@@ -75,7 +75,11 @@ type ConnectionPolicy struct {
 }
 
 func (cp *ConnectionPolicy) buildStandardTLSConfig(handle caddy2.Handle) error {
-	tlsApp := handle.App("tls").(*TLS)
+	tlsAppIface, err := handle.App("tls")
+	if err != nil {
+		return fmt.Errorf("getting tls app: %v", err)
+	}
+	tlsApp := tlsAppIface.(*TLS)
 
 	cfg := &tls.Config{
 		NextProtos:               cp.ALPN,