Repository: heptio/contour (go)

Issue:
Service quickstart URL from projectcontour.io
Currently our quickstart url (the one we tell people `kubectl apply -f $URL`) is served from the readme in github's master tag. Because of a phase ordering issue the master branch's version of this url points to itself, not latest.

What I propose we do is replace this url with a url on projectcontour.io. This is the URL we use everywhere as the canonical way to install the latest version of contour. This will break the dependency on github's master branch and insulate us from the pending repo move.

Netlify has a syntax for supporting redirects; we'll use that to turn the url hosted on projectcontour.io into our own redirector and then point the _stable_ url back to what we consider to be latest, either :latest, or `v0.15.0`.

Pull request description:
site: redirect /quickstart/contour.yaml to most recent release
Fixes #1464

Signed-off-by: Dave Cheney <dave@cheney.net>

Patch:
diff --git a/Makefile b/Makefile
index 771313eb..64450bde 100644
--- a/Makefile
+++ b/Makefile
@@ -13,7 +13,8 @@ TAG_LATEST ?= false
 # LOCALIP as an env var before running 'make local' will solve that.
 LOCALIP ?= $(shell ifconfig | grep inet | grep -v '::' | grep -v 127.0.0.1 | head -n1 | awk '{print $$2}')
 
-GIT_REF = $(shell git rev-parse --short=8 --verify HEAD)
+# Sets GIT_REF to a tag if it's present, otherwise the short rev.
+GIT_REF = $(shell git describe --tags || git rev-parse --short=8 --verify HEAD)
 VERSION ?= $(GIT_REF)
 
 export GO111MODULE=on
diff --git a/docs/making-a-release.md b/docs/making-a-release.md
new file mode 100644
index 00000000..7386e14d
--- /dev/null
+++ b/docs/making-a-release.md
@@ -0,0 +1,85 @@
+# Making a Contour release
+
+This page documents the process for releasing a new version of Contour.
+
+The release types are as follows. All are tagged from the same release branch.
+
+- Alpha releases.
+- Beta releases.
+- RC (Release Candidate) releases.
+- Final releases.
+- Patch releases.
+
+## Branch for release
+
+As contours master branch is under active development, all releases are made from a branch.
+Create a release branch locally, if it has not been created for a beta release, like so
+
+```sh
+% git checkout -b release-0.15
+```
+
+If you are doing a patch release on an existing branch, skip this step and just checkout the branch instead.
+
+This branch is used for all the release types, alpha through final.
+Each on of the release types is just a different tag on the release branch./
+
+## Alpha, beta, and release candidates
+
+The steps for an non-production release are
+
+- Checkout the release branch
+
+```sh
+% git checkout -b release-0.15
+```
+
+- Tag the head of release branch with the relevant release tag (in this case `alpha.1`), and push
+
+```sh
+% git tag -a v0.15.0-alpha.1 -m 'contour 0.15.0 alpha 1'
+% git push --tags
+```
+
+Once the tag is present on the release branch, Github Actions will build the tag and push it to Docker Hub for you.
+Then, you are done until we move to a final release.
+
+## Final release
+
+### Release tag from release branch
+
+Tag the head of your release branch with the release tag, and push
+
+```sh
+% git tag -a v0.15.0 -m 'contour 0.15.0'
+% git push --tags
+```
+
+## Patch release
+
+### Cherry-pick required commits into the release branch
+
+Get any required changes into the release branch by whatever means you choose.
+
+### Release tag from reelase branch
+
+Tag the head of your release branch with the release tag, and push
+
+```sh
+% git tag -a v0.15.1 -m 'contour 0.15.1'
+% git push --tags
+```
+
+## Finishing up
+
+If you've made a production release (that is, a final release or a patch release), you have a couple of things left to do.
+
+### Do the Github release and write release notes
+
+Now you have a tag pushed to Github, go to the release tab on github, select the tag and write up your release notes. For patch releases, include the previous release notes below the new ones.
+
+### Toot your horn
+
+- Post a note to the #contour channel on k8s slack, also update the /topic with the current release number
+- Post a note to the #project-contour channel on the vmware slack, also update the /topic with the current release number
+- Send an email to the project-contour mailing list
diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
index 27adaf53..6813b24f 100644
--- a/docs/troubleshooting.md
+++ b/docs/troubleshooting.md
@@ -9,45 +9,50 @@ For example, if you have not configured any TLS ingress objects then Contour doe
 Because the HTTP and HTTPS listeners both use the same code, if you have no ingress objects deployed in your cluster, or if no ingress objects are permitted to talk on HTTP, then Envoy does not listen on port 8080 (80 in the service object).
 
 To test whether Contour is correctly deployed you can deploy the kuard example service:
-```
-$ kubectl apply -f https://j.hept.io/contour-kuard-example
+
+```sh
+% kubectl apply -f https://raw.githubusercontent.com/heptio/contour/master/examples/example-workload/kuard.yaml
 ```
 
 ## Access the Envoy admin interface remotely
 
 Getting access to the Envoy admin interface can be useful for diagnosing issues with routing or cluster health.
 
-The Envoy admin interface is bound by default to `http://127.0.0.1:9001`. 
+The Envoy admin interface is bound by default to `http://127.0.0.1:9001`.
 To access it from your workstation use `kubectl port-forward` like so,
-```
-# Get one of the pods that matches the examples/daemonset
-CONTOUR_POD=$(kubectl -n heptio-contour get pod -l app=contour -o jsonpath='{.items[0].metadata.name}')
+
+```sh
+# Get one of the pods that matches the Envoy daemonset
+ENVOY_POD=$(kubectl -n heptio-contour get pod -l app=envoy -o name | head -1)
 # Do the port forward to that pod
-kubectl -n heptio-contour port-forward $CONTOUR_POD 9001
+kubectl -n heptio-contour port-forward $ENVOY_POD 9001
 ```
+
 Then navigate to [http://127.0.0.1:9001/](http://127.0.0.1:9001/) to access the admin interface for the Envoy container running on that pod.
 
 ## Accessing Contour's /debug/pprof service
 
-Contour exposes the [net/http/pprof][5] handlers for `go tool pprof` and `go tool trace` by default on `127.0.0.1:6060`.
-This service is useful for profiling Contour. 
+Contour exposes the [net/http/pprof](https://golang.org/pkg/net/http/pprof/) handlers for `go tool pprof` and `go tool trace` by default on `127.0.0.1:6060`.
+This service is useful for profiling Contour.
 To access it from your workstation use `kubectl port-forward` like so,
-```
-# Get one of the pods that matches the examples/daemonset
-CONTOUR_POD=$(kubectl -n heptio-contour get pod -l app=contour -o jsonpath='{.items[0].metadata.name}')
+
+```sh
+# Get one of the pods that matches the Contour deployment
+CONTOUR_POD=$(kubectl -n heptio-contour get pod -l app=contour -o name | head -1)
 # Do the port forward to that pod
 kubectl -n heptio-contour port-forward $CONTOUR_POD 6060
 ```
 
 ## Visualizing Contour's internal directed acyclic graph (DAG)
 
-Contour models its configuration using a DAG, which can be visualized through a debug endpoint that outputs the DAG in [DOT][6] format. To visualize the graph, you must have [`graphviz`][7] installed on your system.
+Contour models its configuration using a DAG, which can be visualized through a debug endpoint that outputs the DAG in [DOT](https://en.wikipedia.org/wiki/DOT_(graph_description_language)) format.
+To visualize the graph, you must have [`graphviz`](https://graphviz.gitlab.io/) installed on your system.
 
 To download the graph and save it as a PNG:
 
 ```sh
 # Port forward into the contour pod
-CONTOUR_POD=$(kubectl -n heptio-contour get pod -l app=contour -o jsonpath='{.items[0].metadata.name}')
+CONTOUR_POD=$(kubectl -n heptio-contour get pod -l app=contour -o name | head -1)
 # Do the port forward to that pod
 kubectl -n heptio-contour port-forward $CONTOUR_POD 6060
 # Download and store the DAG in png format
@@ -59,49 +64,24 @@ The following is an example of a DAG that maps `http://kuard.local:80/` to the
 
 ![Sample DAG](./dag-img/kuard-dag.png "Sample DAG")
 
-
 ## Interrogate Contour's gRPC API
 
 Sometimes it's helpful to be able to interrogate Contour to find out exactly the data it is sending to Envoy.
 Contour ships with a `contour cli` subcommand which can be used for this purpose.
 
-Because Contour runs as a sidecar with Envoy, you must run `contour cli` commands _inside_ the pod.
-The easiest way do this is via `kubectl exec`
-```
+Because Contour secures its communications with Envoy using Secrets in the cluster, the easiest way is to run `contour cli` commands _inside_ the pod.
+Do this is via `kubectl exec`:
+
+```sh
 # Get one of the pods that matches the examples/daemonset
 CONTOUR_POD=$(kubectl -n heptio-contour get pod -l app=contour -o jsonpath='{.items[0].metadata.name}')
 # Do the port forward to that pod
-kubectl -n heptio-contour exec $CONTOUR_POD -c contour contour cli lds
+kubectl -n heptio-contour exec $CONTOUR_POD -c contour -- contour cli lds --cafile=/ca/cacert.pem --cert-file=/certs/tls.crt --key-file=/certs/tls.key
 ```
+
 Which will stream changes to the LDS api endpoint to your terminal.
 Replace `contour cli lds` with `contour cli rds` for RDS, `contour cli cds` for CDS, and `contour cli eds` for EDS.
 
 ## I've deployed on Minikube or kind and nothing seems to work
 
-See [the deployment documentation][3] for some tips on using these two deployment options successfully.
-
-## How can I deploy a Contour daemonset directly onto ports 80 or 443?
-
-To set envoy to run on port 80 and port 443, the following additional 
-arguments may be passed into the contour command:
-
-```yaml
-  command: ["contour"]
-  args: 
-  - serve
-  - --incluster
-  - --envoy-service-http-port=80
-  - --envoy-service-https-port=443
-```
-
-See [Issue #547][4]
-
-[0]: https://github.com/jetstack/kube-lego
-[1]: https://github.com/heptio/contour/issues/210
-[2]: https://github.com/envoyproxy/envoy/issues/1269
-[3]: deploy-options.md
-[4]: https://github.com/heptio/contour/issues/547
-[5]: https://golang.org/pkg/net/http/pprof/
-[6]: https://en.wikipedia.org/wiki/DOT_(graph_description_language)
-[7]: https://graphviz.gitlab.io/
-[8]: https://github.com/jetstack/cert-manager
+See [the deployment documentation](deploy-options.md) for some tips on using these two deployment options successfully.
diff --git a/docs/upgrading.md b/docs/upgrading.md
index 95eec68e..5e556bcb 100644
--- a/docs/upgrading.md
+++ b/docs/upgrading.md
@@ -40,6 +40,10 @@ kubectl apply -f examples/<your-desired-deployment>
 If you're using a `LoadBalancer` Service, (which most of the examples do) deleting and recreating may change the public IP assigned by your cloud provider.
 You'll need to re-check where your DNS names are pointing as well, using [Get your hostname or IP address](./deploy-options.md#get_your_hostname_or_ip_address).
 
+### Note
+
+If you deployed Contour into a different namespace than heptio-contour with a standard example, please delete that namespace. Then in your editor of choice do a search and replace for `heptio-contour` and replace it with your preferred name space and apply the updated manifest.
+
 ## The less easy way
 
 This section contains information for administrators who wish to apply the Contour 0.14 to 0.15 changes manually.
@@ -123,4 +127,4 @@ and checking the annotations that store exact details using
 
 ```
 kubectl get configmap -n heptio-contour -o yaml contour
-```
+```
\ No newline at end of file
diff --git a/examples/common/contour-config.yaml b/examples/common/contour-config.yaml
index aac49d80..39d2bec0 100644
--- a/examples/common/contour-config.yaml
+++ b/examples/common/contour-config.yaml
@@ -15,7 +15,7 @@ data:
     # disablePermitInsecure: false
     tls:
       # minimum TLS version that Contour will negotiate
-      # minimumProtocolVersion: "1.1"
+      # minimum-protocol-version: "1.1"
     # The following config shows the defaults for the leader election.
     # leaderelection:
       # configmap-name: contour
diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml
index f083464e..9aed9435 100644
--- a/examples/render/contour.yaml
+++ b/examples/render/contour.yaml
@@ -341,7 +341,7 @@ data:
     # disablePermitInsecure: false
     tls:
       # minimum TLS version that Contour will negotiate
-      # minimumProtocolVersion: "1.1"
+      # minimum-protocol-version: "1.1"
     # The following config shows the defaults for the leader election.
     # leaderelection:
       # configmap-name: contour
diff --git a/examples/render/daemonset-rbac.yaml b/examples/render/daemonset-rbac.yaml
index 0fa2b2f8..9ccdf851 100644
--- a/examples/render/daemonset-rbac.yaml
+++ b/examples/render/daemonset-rbac.yaml
@@ -341,7 +341,7 @@ data:
     # disablePermitInsecure: false
     tls:
       # minimum TLS version that Contour will negotiate
-      # minimumProtocolVersion: "1.1"
+      # minimum-protocol-version: "1.1"
     # The following config shows the defaults for the leader election.
     # leaderelection:
       # configmap-name: contour
diff --git a/examples/render/deployment-rbac.yaml b/examples/render/deployment-rbac.yaml
index 7ac093e6..540675b0 100644
--- a/examples/render/deployment-rbac.yaml
+++ b/examples/render/deployment-rbac.yaml
@@ -341,7 +341,7 @@ data:
     # disablePermitInsecure: false
     tls:
       # minimum TLS version that Contour will negotiate
-      # minimumProtocolVersion: "1.1"
+      # minimum-protocol-version: "1.1"
     # The following config shows the defaults for the leader election.
     # leaderelection:
       # configmap-name: contour
diff --git a/internal/dag/builder.go b/internal/dag/builder.go
index 6481e81f..a8c8ddb5 100644
--- a/internal/dag/builder.go
+++ b/internal/dag/builder.go
@@ -322,7 +322,15 @@ func (b *Builder) computeIngresses() {
 		rules := rulesFromSpec(ing.Spec)
 
 		for _, rule := range rules {
-			host := stringOrDefault(rule.Host, "*")
+			if strings.Contains(rule.Host, "*") {
+				// reject hosts with wildcard characters.
+				continue
+			}
+			host := rule.Host
+			if host == "" {
+				// if host name is blank, rewrite to Envoy's * default host.
+				host = "*"
+			}
 			for _, httppath := range httppaths(rule) {
 				path := stringOrDefault(httppath.Path, "/")
 				be := httppath.Backend
@@ -366,63 +374,63 @@ func (b *Builder) computeIngresses() {
 
 func (b *Builder) computeIngressRoutes() {
 	for _, ir := range b.validIngressRoutes() {
-		if ir.Spec.VirtualHost == nil {
-			// mark delegate ingressroute orphaned.
-			b.setOrphaned(ir.Name, ir.Namespace)
-			continue
-		}
+		b.computeIngressRoute(ir)
+	}
+}
 
-		// ensure root ingressroute lives in allowed namespace
-		if !b.rootAllowed(ir.Namespace) {
-			b.setStatus(Status{Object: ir, Status: StatusInvalid, Description: "root IngressRoute cannot be defined in this namespace"})
-			continue
-		}
+func (b *Builder) computeIngressRoute(ir *ingressroutev1.IngressRoute) {
+	if ir.Spec.VirtualHost == nil {
+		// mark delegate ingressroute orphaned.
+		b.setOrphaned(ir.Name, ir.Namespace)
+		return
+	}
 
-		host := ir.Spec.VirtualHost.Fqdn
-		if isBlank(host) {
-			b.setStatus(Status{Object: ir, Status: StatusInvalid, Description: "Spec.VirtualHost.Fqdn must be specified"})
-			continue
-		}
+	// ensure root ingressroute lives in allowed namespace
+	if !b.rootAllowed(ir.Namespace) {
+		b.setStatus(Status{Object: ir, Status: StatusInvalid, Description: "root IngressRoute cannot be defined in this namespace"})
+		return
+	}
 
-		if strings.Contains(host, "*") {
-			b.setStatus(Status{Object: ir, Status: StatusInvalid, Description: fmt.Sprintf("Spec.VirtualHost.Fqdn %q cannot use wildcards", host), Vhost: host})
-			continue
-		}
+	host := ir.Spec.VirtualHost.Fqdn
+	if isBlank(host) {
+		b.setStatus(Status{Object: ir, Status: StatusInvalid, Description: "Spec.VirtualHost.Fqdn must be specified"})
+		return
+	}
 
-		enforceTLS, passthrough := b.configureSecureVirtualHost(ir)
-		if ir.Spec.TCPProxy != nil && (passthrough || enforceTLS) {
-			b.processTCPProxy(ir, nil, host)
-		}
-		b.processIngressRoutes(ir, "", nil, host, ir.Spec.TCPProxy == nil && enforceTLS)
+	if strings.Contains(host, "*") {
+		b.setStatus(Status{Object: ir, Status: StatusInvalid, Description: fmt.Sprintf("Spec.VirtualHost.Fqdn %q cannot use wildcards", host), Vhost: host})
+		return
 	}
-}
 
-func (b *Builder) configureSecureVirtualHost(ir *ingressroutev1.IngressRoute) (enforceTLS, passthrough bool) {
-	tls := ir.Spec.VirtualHost.TLS
-	if tls == nil {
-		return false, false
-	}
-	m := splitSecret(tls.SecretName, ir.Namespace)
-	sec := b.lookupSecret(m, validSecret)
-	if sec != nil {
-		if !b.delegationPermitted(m, ir.Namespace) {
-			b.setStatus(Status{Object: ir, Status: StatusInvalid, Description: fmt.Sprintf("%s: certificate delegation not permitted", tls.SecretName)})
-			return false, false
+	var enforceTLS, passthrough bool
+	if tls := ir.Spec.VirtualHost.TLS; tls != nil {
+		m := splitSecret(tls.SecretName, ir.Namespace)
+		sec := b.lookupSecret(m, validSecret)
+		if sec != nil {
+			if !b.delegationPermitted(m, ir.Namespace) {
+				b.setStatus(Status{Object: ir, Status: StatusInvalid, Description: fmt.Sprintf("%s: certificate delegation not permitted", tls.SecretName)})
+				return
+			}
+			svhost := b.lookupSecureVirtualHost(ir.Spec.VirtualHost.Fqdn)
+			svhost.Secret = sec
+			svhost.MinProtoVersion = MinProtoVersion(ir.Spec.VirtualHost.TLS.MinimumProtocolVersion)
+			enforceTLS = true
+		}
+		// passthrough is true if tls.secretName is not present, and
+		// tls.passthrough is set to true.
+		passthrough = isBlank(tls.SecretName) && tls.Passthrough
+
+		// If not passthrough and secret is invalid, then set status
+		if sec == nil && !passthrough {
+			b.setStatus(Status{Object: ir, Status: StatusInvalid, Description: fmt.Sprintf("TLS Secret [%s] not found or is malformed", tls.SecretName)})
+			return
 		}
-		svhost := b.lookupSecureVirtualHost(ir.Spec.VirtualHost.Fqdn)
-		svhost.Secret = sec
-		svhost.MinProtoVersion = MinProtoVersion(ir.Spec.VirtualHost.TLS.MinimumProtocolVersion)
-		enforceTLS = true
 	}
-	// passthrough is true if tls.secretName is not present, and
-	// tls.passthrough is set to true.
-	passthrough = isBlank(tls.SecretName) && tls.Passthrough
 
-	// If not passthrough and secret is invalid, then set status
-	if sec == nil && !passthrough {
-		b.setStatus(Status{Object: ir, Status: StatusInvalid, Description: fmt.Sprintf("TLS Secret [%s] not found or is malformed", tls.SecretName)})
+	if ir.Spec.TCPProxy != nil && (passthrough || enforceTLS) {
+		b.processTCPProxy(ir, nil, host)
 	}
-	return enforceTLS, passthrough
+	b.processIngressRoutes(ir, "", nil, host, ir.Spec.TCPProxy == nil && enforceTLS)
 }
 
 func (b *Builder) computeHTTPLoadBalancers() {
@@ -451,7 +459,6 @@ func (b *Builder) computeHTTPLoadBalancers() {
 		}
 
 		var enforceTLS, passthrough bool
-
 		if tls := httplb.Spec.VirtualHost.TLS; tls != nil {
 			// attach secrets to TLS enabled vhosts
 			m := splitSecret(tls.SecretName, httplb.Namespace)
diff --git a/site/_redirect b/site/_redirect
new file mode 100644
index 00000000..43aa0f4f
--- /dev/null
+++ b/site/_redirect
@@ -0,0 +1,3 @@
+# redirect /quickstart/contour.yaml to the deployment that matches :latest
+# kubectl apply https://projectcontour.io/quickstart/contour.yaml
+/quickstart/contour.yaml	https://raw.githubusercontent.com/heptio/contour/release-0.15/examples/render/deployment-rbac.yaml	302


Test patch:
diff --git a/.github/workflows/build_tag_no_latest.yaml b/.github/workflows/build_tag_no_latest.yaml
new file mode 100644
index 00000000..9a8a96f2
--- /dev/null
+++ b/.github/workflows/build_tag_no_latest.yaml
@@ -0,0 +1,25 @@
+name: Build and push a release without :latest
+
+on:
+  push:
+    tags:
+      # Although these *look* like regex matches, they're not!
+      # They are Go path.Match() expressions.
+      # See https://golang.org/pkg/path/#Match for details.
+      - 'v[0-9]*.[0-9]*.[0-9]*beta*'
+      - 'v[0-9]*.[0-9]*.[0-9]*alpha*'
+      - 'v[0-9]*.[0-9]*.[0-9]*rc*'
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - name: Build and Push to registry
+      env:
+        DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
+        DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
+        TAG_LATEST: "false"
+      run: |
+        echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
+        make push
+        docker logout
diff --git a/.github/workflows/build_tag_with_latest.yaml b/.github/workflows/build_tag_with_latest.yaml
new file mode 100644
index 00000000..6aa892ae
--- /dev/null
+++ b/.github/workflows/build_tag_with_latest.yaml
@@ -0,0 +1,26 @@
+name: Build and push a release with :latest
+
+on:
+  push:
+    tags:
+      # Although these *look* like regex matches, they're not!
+      # They are Go path.Match() expressions.
+      # See https://golang.org/pkg/path/#Match for details.
+      - 'v[0-9]*.[0-9]*.[0-9]'
+      - 'v[0-9]*.[0-9]*.[0-9][0-9]'
+      - 'v[0-9]*.[0-9]*.[0-9][0-9][0-9]'
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - name: Build and Push to registry
+      env:
+        DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
+        DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
+        TAG_LATEST: "true"
+      run: |
+        echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
+        make push
+        docker logout
diff --git a/internal/dag/builder_test.go b/internal/dag/builder_test.go
index 5b9fd105..999bb85f 100644
--- a/internal/dag/builder_test.go
+++ b/internal/dag/builder_test.go
@@ -589,6 +589,51 @@ func TestDAGInsert(t *testing.T) {
 		},
 	}
 
+	i16 := &v1beta1.Ingress{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "wildcards",
+			Namespace: "default",
+		},
+		Spec: v1beta1.IngressSpec{
+			Rules: []v1beta1.IngressRule{{
+				// no hostname
+				IngressRuleValue: v1beta1.IngressRuleValue{
+					HTTP: &v1beta1.HTTPIngressRuleValue{
+						Paths: []v1beta1.HTTPIngressPath{{
+							Backend: v1beta1.IngressBackend{
+								ServiceName: "kuard",
+								ServicePort: intstr.FromString("http")},
+						}},
+					},
+				},
+			}, {
+				Host: "*",
+				IngressRuleValue: v1beta1.IngressRuleValue{
+					HTTP: &v1beta1.HTTPIngressRuleValue{
+						Paths: []v1beta1.HTTPIngressPath{{
+							Backend: v1beta1.IngressBackend{
+								ServiceName: "kuard",
+								ServicePort: intstr.FromString("http"),
+							},
+						}},
+					},
+				},
+			}, {
+				Host: "*.example.com",
+				IngressRuleValue: v1beta1.IngressRuleValue{
+					HTTP: &v1beta1.HTTPIngressRuleValue{
+						Paths: []v1beta1.HTTPIngressPath{{
+							Backend: v1beta1.IngressBackend{
+								ServiceName: "kuarder",
+								ServicePort: intstr.FromInt(8080),
+							},
+						}},
+					},
+				},
+			}},
+		},
+	}
+
 	// s3a and b have http/2 protocol annotations
 	s3a := &v1.Service{
 		ObjectMeta: metav1.ObjectMeta{
@@ -1532,6 +1577,28 @@ func TestDAGInsert(t *testing.T) {
 		},
 	}
 
+	ir19 := &ingressroutev1.IngressRoute{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "app-with-tls-delegation",
+			Namespace: s10.Namespace,
+		},
+		Spec: ingressroutev1.IngressRouteSpec{
+			VirtualHost: &projcontour.VirtualHost{
+				Fqdn: "app-with-tls-delegation.127.0.0.1.nip.io",
+				TLS: &projcontour.TLS{
+					SecretName: "heptio-contour/ssl-cert", // not delegated
+				},
+			},
+			Routes: []ingressroutev1.Route{{
+				Match: "/",
+				Services: []ingressroutev1.Service{{
+					Name: s10.Name,
+					Port: 80,
+				}},
+			}},
+		},
+	}
+
 	httlb1 := &projcontour.HTTPLoadBalancer{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      "example-com",
@@ -2821,6 +2888,12 @@ func TestDAGInsert(t *testing.T) {
 				},
 			),
 		},
+		"insert ingressroute with missing tls delegation should not present port 80": {
+			objs: []interface{}{
+				s10, ir19,
+			},
+			want: listeners(), // no listeners, ir19 is invalid
+		},
 		"insert root ingress route and delegate ingress route": {
 			objs: []interface{}{
 				ir5, s4, ir4, s5, ir3,
@@ -2934,7 +3007,7 @@ func TestDAGInsert(t *testing.T) {
 				},
 			),
 		},
-		"insert ingressroute with regex route": {
+		"insert ingress with regex route": {
 			objs: []interface{}{
 				i15,
 				s1,
@@ -2953,6 +3026,21 @@ func TestDAGInsert(t *testing.T) {
 				},
 			),
 		},
+		// issue 1234
+		"insert ingress with wildcard hostnames": {
+			objs: []interface{}{
+				s1,
+				i16,
+			},
+			want: listeners(
+				&Listener{
+					Port: 80,
+					VirtualHosts: virtualhosts(
+						virtualhost("*", prefixroute("/", service(s1))),
+					),
+				},
+			),
+		},
 		"insert ingress overlay": {
 			objs: []interface{}{
 				i13a, i13b, sec13, s13a, s13b,
diff --git a/internal/dag/status_test.go b/internal/dag/status_test.go
index 29efba81..5b874e25 100644
--- a/internal/dag/status_test.go
+++ b/internal/dag/status_test.go
@@ -702,6 +702,28 @@ func TestDAGIngressRouteStatus(t *testing.T) {
 		},
 	}
 
+	ir28 := &ingressroutev1.IngressRoute{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "app-with-tls-delegation",
+			Namespace: s10.Namespace,
+		},
+		Spec: ingressroutev1.IngressRouteSpec{
+			VirtualHost: &projcontour.VirtualHost{
+				Fqdn: "app-with-tls-delegation.127.0.0.1.nip.io",
+				TLS: &projcontour.TLS{
+					SecretName: "heptio-contour/ssl-cert", // not delegated
+				},
+			},
+			Routes: []ingressroutev1.Route{{
+				Match: "/",
+				Services: []ingressroutev1.Service{{
+					Name: s10.Name,
+					Port: 80,
+				}},
+			}},
+		},
+	}
+
 	tests := map[string]struct {
 		objs []interface{}
 		want map[Meta]Status
@@ -937,6 +959,20 @@ func TestDAGIngressRouteStatus(t *testing.T) {
 				},
 			},
 		},
+		// issue 1452
+		"ingressroute with missing secret delegation should be invalid": {
+			objs: []interface{}{
+				s10,
+				ir28,
+			},
+			want: map[Meta]Status{
+				{name: ir28.Name, namespace: ir28.Namespace}: {
+					Object:      ir28,
+					Status:      StatusInvalid,
+					Description: "TLS Secret [heptio-contour/ssl-cert] not found or is malformed",
+				},
+			},
+		},
 	}
 
 	for name, tc := range tests {
diff --git a/internal/e2e/lds_test.go b/internal/e2e/lds_test.go
index 802f3e55..a0c5e93d 100644
--- a/internal/e2e/lds_test.go
+++ b/internal/e2e/lds_test.go
@@ -316,25 +316,39 @@ func TestIngressRouteTLSListener(t *testing.T) {
 		},
 	}
 
+	svc1 := &v1.Service{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "backend",
+			Namespace: secret1.Namespace,
+		},
+		Spec: v1.ServiceSpec{
+			Ports: []v1.ServicePort{{
+				Name:     "http",
+				Protocol: "TCP",
+				Port:     80,
+			}},
+		},
+	}
+
 	// i1 is a tls ingressroute
 	i1 := &ingressroutev1.IngressRoute{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      "simple",
-			Namespace: "default",
+			Namespace: secret1.Namespace,
 		},
 		Spec: ingressroutev1.IngressRouteSpec{
 			VirtualHost: &projcontour.VirtualHost{
 				Fqdn: "kuard.example.com",
 				TLS: &projcontour.TLS{
-					SecretName:             "secret",
+					SecretName:             secret1.Name,
 					MinimumProtocolVersion: "1.1",
 				},
 			},
 			Routes: []ingressroutev1.Route{{
 				Match: "/",
 				Services: []ingressroutev1.Service{{
-					Name: "backend",
-					Port: 80,
+					Name: svc1.Name,
+					Port: int(svc1.Spec.Ports[0].Port),
 				}},
 			}},
 		},
@@ -344,40 +358,26 @@ func TestIngressRouteTLSListener(t *testing.T) {
 	i2 := &ingressroutev1.IngressRoute{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      "simple",
-			Namespace: "default",
+			Namespace: secret1.Namespace,
 		},
 		Spec: ingressroutev1.IngressRouteSpec{
 			VirtualHost: &projcontour.VirtualHost{
 				Fqdn: "kuard.example.com",
 				TLS: &projcontour.TLS{
-					SecretName:             "secret",
+					SecretName:             secret1.Name,
 					MinimumProtocolVersion: "1.3",
 				},
 			},
 			Routes: []ingressroutev1.Route{{
 				Match: "/",
 				Services: []ingressroutev1.Service{{
-					Name: "backend",
-					Port: 80,
+					Name: svc1.Name,
+					Port: int(svc1.Spec.Ports[0].Port),
 				}},
 			}},
 		},
 	}
 
-	svc1 := &v1.Service{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "backend",
-			Namespace: "default",
-		},
-		Spec: v1.ServiceSpec{
-			Ports: []v1.ServicePort{{
-				Name:     "http",
-				Protocol: "TCP",
-				Port:     80,
-			}},
-		},
-	}
-
 	// add secret
 	rh.OnAdd(secret1)
 
@@ -425,18 +425,12 @@ func TestIngressRouteTLSListener(t *testing.T) {
 		Nonce:   "1",
 	}, streamLDS(t, cc))
 
-	// delete secret and assert that ingress_https is removed
+	// delete secret and assert both listeners are removed because the
+	// ingressroute is no longer valid.
 	rh.OnDelete(secret1)
 	assertEqual(t, &v2.DiscoveryResponse{
 		VersionInfo: "2",
 		Resources: resources(t,
-			&v2.Listener{
-				Name:    "ingress_http",
-				Address: envoy.SocketAddress("0.0.0.0", 8080),
-				FilterChains: envoy.FilterChains(
-					envoy.HTTPConnectionManager("ingress_http", "/dev/stdout"),
-				),
-			},
 			staticListener(),
 		),
 		TypeUrl: listenerType,
@@ -1511,19 +1505,10 @@ func TestIngressRouteTLSCertificateDelegation(t *testing.T) {
 		},
 	})
 
-	ingress_http := &v2.Listener{
-		Name:    "ingress_http",
-		Address: envoy.SocketAddress("0.0.0.0", 8080),
-		FilterChains: envoy.FilterChains(
-			envoy.HTTPConnectionManager("ingress_http", "/dev/stdout"),
-		),
-	}
-
-	// assert there is no ingress_https because there is no matching secret.
+	// assert there are no listeners
 	assertEqual(t, &v2.DiscoveryResponse{
 		VersionInfo: "1",
 		Resources: resources(t,
-			ingress_http,
 			staticListener(),
 		),
 		TypeUrl: listenerType,
@@ -1547,6 +1532,14 @@ func TestIngressRouteTLSCertificateDelegation(t *testing.T) {
 	}
 	rh.OnAdd(t1)
 
+	ingress_http := &v2.Listener{
+		Name:    "ingress_http",
+		Address: envoy.SocketAddress("0.0.0.0", 8080),
+		FilterChains: envoy.FilterChains(
+			envoy.HTTPConnectionManager("ingress_http", "/dev/stdout"),
+		),
+	}
+
 	ingress_https := &v2.Listener{
 		Name:    "ingress_https",
 		Address: envoy.SocketAddress("0.0.0.0", 8443),
@@ -1615,7 +1608,6 @@ func TestIngressRouteTLSCertificateDelegation(t *testing.T) {
 	assertEqual(t, &v2.DiscoveryResponse{
 		VersionInfo: "4",
 		Resources: resources(t,
-			ingress_http,
 			staticListener(),
 		),
 		TypeUrl: listenerType,
@@ -1642,7 +1634,6 @@ func TestIngressRouteTLSCertificateDelegation(t *testing.T) {
 	assertEqual(t, &v2.DiscoveryResponse{
 		VersionInfo: "5",
 		Resources: resources(t,
-			ingress_http,
 			staticListener(),
 		),
 		TypeUrl: listenerType,