Repository: replicatedhq/troubleshoot
Pull request #977 (2023-01-23 23:05:28)

chore(deps): bump k8s.io/apiextensions-apiserver from 0.26.0 to 0.26.1
Bumps [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver) from 0.26.0 to 0.26.1.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/kubernetes/apiextensions-apiserver/commit/e8bd314947f6583828d5236af1b37e5bbf03835e"><code>e8bd314</code></a> Update dependencies to v0.26.1 tag</li>
<li><a href="https://github.com/kubernetes/apiextensions-apiserver/commit/33db789573b1d84aca51d74d32c1834fc09c63a9"><code>33db789</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/kubernetes/apiextensions-apiserver/issues/114861">#114861</a> from jpbetz/release-1.26</li>
<li><a href="https://github.com/kubernetes/apiextensions-apiserver/commit/a06e03da14b8a3f58a5d95a73add4f88c5edd8e3"><code>a06e03d</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/kubernetes/apiextensions-apiserver/issues/114927">#114927</a> from jkh52/release-1.26-knp-metrics</li>
<li><a href="https://github.com/kubernetes/apiextensions-apiserver/commit/0859963f87c3d400ed4aaed7ea741675b34335e7"><code>0859963</code></a> Cherry pick 114857 to release-1.26</li>
<li><a href="https://github.com/kubernetes/apiextensions-apiserver/commit/51838855cd1d68d9f590e0ceff8e274815425b31"><code>5183885</code></a> Bump konnectivity-client to v0.0.35</li>
<li>See full diff in <a href="https://github.com/kubernetes/apiextensions-apiserver/compare/v0.26.0...v0.26.1">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=k8s.io/apiextensions-apiserver&package-manager=go_modules&previous-version=0.26.0&new-version=0.26.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/config/crds/troubleshoot.sh_analyzers.yaml b/config/crds/troubleshoot.sh_analyzers.yaml
index 430671c4..45e696dd 100644
--- a/config/crds/troubleshoot.sh_analyzers.yaml
+++ b/config/crds/troubleshoot.sh_analyzers.yaml
@@ -2302,6 +2302,55 @@ spec:
                       required:
                       - outcomes
                       type: object
+                    udpPortStatus:
+                      properties:
+                        annotations:
+                          additionalProperties:
+                            type: string
+                          type: object
+                        checkName:
+                          type: string
+                        collectorName:
+                          type: string
+                        exclude:
+                          type: BoolString
+                        outcomes:
+                          items:
+                            properties:
+                              fail:
+                                properties:
+                                  message:
+                                    type: string
+                                  uri:
+                                    type: string
+                                  when:
+                                    type: string
+                                type: object
+                              pass:
+                                properties:
+                                  message:
+                                    type: string
+                                  uri:
+                                    type: string
+                                  when:
+                                    type: string
+                                type: object
+                              warn:
+                                properties:
+                                  message:
+                                    type: string
+                                  uri:
+                                    type: string
+                                  when:
+                                    type: string
+                                type: object
+                            type: object
+                          type: array
+                        strict:
+                          type: BoolString
+                      required:
+                      - outcomes
+                      type: object
                   type: object
                 type: array
             type: object
diff --git a/config/crds/troubleshoot.sh_hostcollectors.yaml b/config/crds/troubleshoot.sh_hostcollectors.yaml
index 4df8b701..913d2151 100644
--- a/config/crds/troubleshoot.sh_hostcollectors.yaml
+++ b/config/crds/troubleshoot.sh_hostcollectors.yaml
@@ -879,6 +879,55 @@ spec:
                       required:
                       - outcomes
                       type: object
+                    udpPortStatus:
+                      properties:
+                        annotations:
+                          additionalProperties:
+                            type: string
+                          type: object
+                        checkName:
+                          type: string
+                        collectorName:
+                          type: string
+                        exclude:
+                          type: BoolString
+                        outcomes:
+                          items:
+                            properties:
+                              fail:
+                                properties:
+                                  message:
+                                    type: string
+                                  uri:
+                                    type: string
+                                  when:
+                                    type: string
+                                type: object
+                              pass:
+                                properties:
+                                  message:
+                                    type: string
+                                  uri:
+                                    type: string
+                                  when:
+                                    type: string
+                                type: object
+                              warn:
+                                properties:
+                                  message:
+                                    type: string
+                                  uri:
+                                    type: string
+                                  when:
+                                    type: string
+                                type: object
+                            type: object
+                          type: array
+                        strict:
+                          type: BoolString
+                      required:
+                      - outcomes
+                      type: object
                   type: object
                 type: array
               collectors:
@@ -1244,6 +1293,19 @@ spec:
                         exclude:
                           type: BoolString
                       type: object
+                    udpPortStatus:
+                      properties:
+                        collectorName:
+                          type: string
+                        exclude:
+                          type: BoolString
+                        interface:
+                          type: string
+                        port:
+                          type: integer
+                      required:
+                      - port
+                      type: object
                   type: object
                 type: array
             type: object
diff --git a/config/crds/troubleshoot.sh_hostpreflights.yaml b/config/crds/troubleshoot.sh_hostpreflights.yaml
index 78ac183c..8dae99b0 100644
--- a/config/crds/troubleshoot.sh_hostpreflights.yaml
+++ b/config/crds/troubleshoot.sh_hostpreflights.yaml
@@ -879,6 +879,55 @@ spec:
                       required:
                       - outcomes
                       type: object
+                    udpPortStatus:
+                      properties:
+                        annotations:
+                          additionalProperties:
+                            type: string
+                          type: object
+                        checkName:
+                          type: string
+                        collectorName:
+                          type: string
+                        exclude:
+                          type: BoolString
+                        outcomes:
+                          items:
+                            properties:
+                              fail:
+                                properties:
+                                  message:
+                                    type: string
+                                  uri:
+                                    type: string
+                                  when:
+                                    type: string
+                                type: object
+                              pass:
+                                properties:
+                                  message:
+                                    type: string
+                                  uri:
+                                    type: string
+                                  when:
+                                    type: string
+                                type: object
+                              warn:
+                                properties:
+                                  message:
+                                    type: string
+                                  uri:
+                                    type: string
+                                  when:
+                                    type: string
+                                type: object
+                            type: object
+                          type: array
+                        strict:
+                          type: BoolString
+                      required:
+                      - outcomes
+                      type: object
                   type: object
                 type: array
               collectors:
@@ -1244,6 +1293,19 @@ spec:
                         exclude:
                           type: BoolString
                       type: object
+                    udpPortStatus:
+                      properties:
+                        collectorName:
+                          type: string
+                        exclude:
+                          type: BoolString
+                        interface:
+                          type: string
+                        port:
+                          type: integer
+                      required:
+                      - port
+                      type: object
                   type: object
                 type: array
               remoteCollectors:
@@ -1519,6 +1581,19 @@ spec:
                         exclude:
                           type: BoolString
                       type: object
+                    udpPortStatus:
+                      properties:
+                        collectorName:
+                          type: string
+                        exclude:
+                          type: BoolString
+                        interface:
+                          type: string
+                        port:
+                          type: integer
+                      required:
+                      - port
+                      type: object
                   type: object
                 type: array
             type: object
diff --git a/config/crds/troubleshoot.sh_preflights.yaml b/config/crds/troubleshoot.sh_preflights.yaml
index c5ad6d7c..e81f11d9 100644
--- a/config/crds/troubleshoot.sh_preflights.yaml
+++ b/config/crds/troubleshoot.sh_preflights.yaml
@@ -10162,6 +10162,19 @@ spec:
                         exclude:
                           type: BoolString
                       type: object
+                    udpPortStatus:
+                      properties:
+                        collectorName:
+                          type: string
+                        exclude:
+                          type: BoolString
+                        interface:
+                          type: string
+                        port:
+                          type: integer
+                      required:
+                      - port
+                      type: object
                   type: object
                 type: array
               uploadResultsTo:
diff --git a/config/crds/troubleshoot.sh_remotecollectors.yaml b/config/crds/troubleshoot.sh_remotecollectors.yaml
index 8218f57c..b7626a60 100644
--- a/config/crds/troubleshoot.sh_remotecollectors.yaml
+++ b/config/crds/troubleshoot.sh_remotecollectors.yaml
@@ -340,6 +340,19 @@ spec:
                         exclude:
                           type: BoolString
                       type: object
+                    udpPortStatus:
+                      properties:
+                        collectorName:
+                          type: string
+                        exclude:
+                          type: BoolString
+                        interface:
+                          type: string
+                        port:
+                          type: integer
+                      required:
+                      - port
+                      type: object
                   type: object
                 type: array
               nodeSelector:
diff --git a/config/crds/troubleshoot.sh_supportbundles.yaml b/config/crds/troubleshoot.sh_supportbundles.yaml
index a8f05e46..7f3250d5 100644
--- a/config/crds/troubleshoot.sh_supportbundles.yaml
+++ b/config/crds/troubleshoot.sh_supportbundles.yaml
@@ -10763,6 +10763,55 @@ spec:
                       required:
                       - outcomes
                       type: object
+                    udpPortStatus:
+                      properties:
+                        annotations:
+                          additionalProperties:
+                            type: string
+                          type: object
+                        checkName:
+                          type: string
+                        collectorName:
+                          type: string
+                        exclude:
+                          type: BoolString
+                        outcomes:
+                          items:
+                            properties:
+                              fail:
+                                properties:
+                                  message:
+                                    type: string
+                                  uri:
+                                    type: string
+                                  when:
+                                    type: string
+                                type: object
+                              pass:
+                                properties:
+                                  message:
+                                    type: string
+                                  uri:
+                                    type: string
+                                  when:
+                                    type: string
+                                type: object
+                              warn:
+                                properties:
+                                  message:
+                                    type: string
+                                  uri:
+                                    type: string
+                                  when:
+                                    type: string
+                                type: object
+                            type: object
+                          type: array
+                        strict:
+                          type: BoolString
+                      required:
+                      - outcomes
+                      type: object
                   type: object
                 type: array
               hostCollectors:
@@ -11128,6 +11177,19 @@ spec:
                         exclude:
                           type: BoolString
                       type: object
+                    udpPortStatus:
+                      properties:
+                        collectorName:
+                          type: string
+                        exclude:
+                          type: BoolString
+                        interface:
+                          type: string
+                        port:
+                          type: integer
+                      required:
+                      - port
+                      type: object
                   type: object
                 type: array
               uri:
diff --git a/examples/collect/host/udp-port.yaml b/examples/collect/host/udp-port.yaml
new file mode 100644
index 00000000..f06a7e6b
--- /dev/null
+++ b/examples/collect/host/udp-port.yaml
@@ -0,0 +1,9 @@
+apiVersion: troubleshoot.sh/v1beta2
+kind: HostCollector
+metadata:
+  name: port
+spec:
+  collectors:
+    - udpPortStatus:
+        collectorName: flannel
+        port: 8472
diff --git a/examples/collect/remote/udp-port.yaml b/examples/collect/remote/udp-port.yaml
new file mode 100644
index 00000000..f06a7e6b
--- /dev/null
+++ b/examples/collect/remote/udp-port.yaml
@@ -0,0 +1,9 @@
+apiVersion: troubleshoot.sh/v1beta2
+kind: HostCollector
+metadata:
+  name: port
+spec:
+  collectors:
+    - udpPortStatus:
+        collectorName: flannel
+        port: 8472
diff --git a/examples/preflight/host/udp-port.yaml b/examples/preflight/host/udp-port.yaml
new file mode 100644
index 00000000..aedb9de7
--- /dev/null
+++ b/examples/preflight/host/udp-port.yaml
@@ -0,0 +1,24 @@
+apiVersion: troubleshoot.sh/v1beta2
+kind: HostPreflight
+metadata:
+  name: port
+spec:
+  collectors:
+    - udpPortStatus:
+        collectorName: flannel
+        port: 8472
+  analyzers:
+    - udpPortStatus:
+        collectorName: flannel
+        outcomes:
+          - fail:
+              when: "address-in-use"
+              message: Another process was already listening on port 8472.
+          - fail:
+              when: "error"
+              message: Unexpected port status
+          - pass:
+              when: "connected"
+              message: Port 8472 is open
+          - warn:
+              message: Unexpected port status
diff --git a/examples/preflight/remote/udp-port.yaml b/examples/preflight/remote/udp-port.yaml
new file mode 100644
index 00000000..aedb9de7
--- /dev/null
+++ b/examples/preflight/remote/udp-port.yaml
@@ -0,0 +1,24 @@
+apiVersion: troubleshoot.sh/v1beta2
+kind: HostPreflight
+metadata:
+  name: port
+spec:
+  collectors:
+    - udpPortStatus:
+        collectorName: flannel
+        port: 8472
+  analyzers:
+    - udpPortStatus:
+        collectorName: flannel
+        outcomes:
+          - fail:
+              when: "address-in-use"
+              message: Another process was already listening on port 8472.
+          - fail:
+              when: "error"
+              message: Unexpected port status
+          - pass:
+              when: "connected"
+              message: Port 8472 is open
+          - warn:
+              message: Unexpected port status
diff --git a/go.mod b/go.mod
index 7491ef46..e74c393f 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ require (
 	github.com/blang/semver/v4 v4.0.0
 	github.com/containers/image/v5 v5.23.1
 	github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2
-	github.com/fatih/color v1.13.0
+	github.com/fatih/color v1.14.1
 	github.com/go-logr/logr v1.2.3
 	github.com/go-redis/redis/v7 v7.4.1
 	github.com/go-sql-driver/mysql v1.7.0
@@ -37,9 +37,9 @@ require (
 	golang.org/x/sync v0.1.0
 	gopkg.in/yaml.v2 v2.4.0
 	k8s.io/api v0.26.1
-	k8s.io/apiextensions-apiserver v0.26.0
+	k8s.io/apiextensions-apiserver v0.26.1
 	k8s.io/apimachinery v0.26.1
-	k8s.io/apiserver v0.26.0
+	k8s.io/apiserver v0.26.1
 	k8s.io/cli-runtime v0.26.0
 	k8s.io/client-go v0.26.1
 	k8s.io/klog/v2 v2.80.1
@@ -132,7 +132,7 @@ require (
 	github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
 	github.com/magiconair/properties v1.8.6 // indirect
 	github.com/mailru/easyjson v0.7.6 // indirect
-	github.com/mattn/go-colorable v0.1.12 // indirect
+	github.com/mattn/go-colorable v0.1.13 // indirect
 	github.com/mattn/go-runewidth v0.0.13 // indirect
 	github.com/mattn/go-shellwords v1.0.12 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
diff --git a/go.sum b/go.sum
index bf1aa37a..787a5bd3 100644
--- a/go.sum
+++ b/go.sum
@@ -394,8 +394,8 @@ github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQL
 github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
 github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
-github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
-github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
+github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
 github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
 github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
@@ -725,12 +725,10 @@ github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYt
 github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
 github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
-github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
-github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
-github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
-github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
 github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
@@ -1281,7 +1279,6 @@ golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1338,7 +1335,6 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1712,8 +1708,8 @@ k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ=
 k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8=
 k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ=
 k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg=
-k8s.io/apiextensions-apiserver v0.26.0 h1:Gy93Xo1eg2ZIkNX/8vy5xviVSxwQulsnUdQ00nEdpDo=
-k8s.io/apiextensions-apiserver v0.26.0/go.mod h1:7ez0LTiyW5nq3vADtK6C3kMESxadD51Bh6uz3JOlqWQ=
+k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI=
+k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM=
 k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
 k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
 k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc=
@@ -1722,8 +1718,8 @@ k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu7
 k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=
 k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM=
 k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q=
-k8s.io/apiserver v0.26.0 h1:q+LqIK5EZwdznGZb8bq0+a+vCqdeEEe4Ux3zsOjbc4o=
-k8s.io/apiserver v0.26.0/go.mod h1:aWhlLD+mU+xRo+zhkvP/gFNbShI4wBDHS33o0+JGI84=
+k8s.io/apiserver v0.26.1 h1:6vmnAqCDO194SVCPU3MU8NcDgSqsUA62tBUSWrFXhsc=
+k8s.io/apiserver v0.26.1/go.mod h1:wr75z634Cv+sifswE9HlAo5FQ7UoUauIICRlOE+5dCg=
 k8s.io/cli-runtime v0.26.0 h1:aQHa1SyUhpqxAw1fY21x2z2OS5RLtMJOCj7tN4oq8mw=
 k8s.io/cli-runtime v0.26.0/go.mod h1:o+4KmwHzO/UK0wepE1qpRk6l3o60/txUZ1fEXWGIKTY=
 k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y=
diff --git a/pkg/analyze/cluster_pod_statuses.go b/pkg/analyze/cluster_pod_statuses.go
index 3b5a9c0c..e643ba74 100644
--- a/pkg/analyze/cluster_pod_statuses.go
+++ b/pkg/analyze/cluster_pod_statuses.go
@@ -10,13 +10,14 @@ import (
 
 	"github.com/pkg/errors"
 	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/replicatedhq/troubleshoot/pkg/constants"
 	"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
 	corev1 "k8s.io/api/core/v1"
 )
 
 func clusterPodStatuses(analyzer *troubleshootv1beta2.ClusterPodStatuses, getChildCollectedFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
 	excludeFiles := []string{}
-	collected, err := getChildCollectedFileContents(filepath.Join("cluster-resources", "pods", "*.json"), excludeFiles)
+	collected, err := getChildCollectedFileContents(filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_PODS, "*.json"), excludeFiles)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to read collected pods")
 	}
diff --git a/pkg/analyze/container_runtime.go b/pkg/analyze/container_runtime.go
index 48654bb2..5c03d5ab 100644
--- a/pkg/analyze/container_runtime.go
+++ b/pkg/analyze/container_runtime.go
@@ -2,16 +2,18 @@ package analyzer
 
 import (
 	"encoding/json"
+	"fmt"
 	"net/url"
 	"strings"
 
 	"github.com/pkg/errors"
 	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/replicatedhq/troubleshoot/pkg/constants"
 	corev1 "k8s.io/api/core/v1"
 )
 
 func analyzeContainerRuntime(analyzer *troubleshootv1beta2.ContainerRuntime, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
-	collected, err := getCollectedFileContents("cluster-resources/nodes.json")
+	collected, err := getCollectedFileContents(fmt.Sprintf("%s/%s.json", constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_NODES))
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to get contents of nodes.json")
 	}
diff --git a/pkg/analyze/crd.go b/pkg/analyze/crd.go
index 75761f3a..c5b751d3 100644
--- a/pkg/analyze/crd.go
+++ b/pkg/analyze/crd.go
@@ -5,11 +5,12 @@ import (
 	"fmt"
 
 	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/replicatedhq/troubleshoot/pkg/constants"
 	apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
 )
 
 func analyzeCustomResourceDefinition(analyzer *troubleshootv1beta2.CustomResourceDefinition, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
-	crdData, err := getCollectedFileContents("cluster-resources/custom-resource-definitions.json")
+	crdData, err := getCollectedFileContents(fmt.Sprintf("%s/%s.json", constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_CUSTOM_RESOURCE_DEFINITIONS))
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/analyze/deployment_status.go b/pkg/analyze/deployment_status.go
index 1c61aa7b..ca405e39 100644
--- a/pkg/analyze/deployment_status.go
+++ b/pkg/analyze/deployment_status.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/pkg/errors"
 	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/replicatedhq/troubleshoot/pkg/constants"
 	appsv1 "k8s.io/api/apps/v1"
 )
 
@@ -20,7 +21,7 @@ func analyzeDeploymentStatus(analyzer *troubleshootv1beta2.DeploymentStatus, get
 
 func analyzeOneDeploymentStatus(analyzer *troubleshootv1beta2.DeploymentStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
 	excludeFiles := []string{}
-	files, err := getFileContents(filepath.Join("cluster-resources", "deployments", fmt.Sprintf("%s.json", analyzer.Namespace)), excludeFiles)
+	files, err := getFileContents(filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_DEPLOYMENTS, fmt.Sprintf("%s.json", analyzer.Namespace)), excludeFiles)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to read collected deployments from namespace")
 	}
@@ -66,15 +67,15 @@ func analyzeAllDeploymentStatuses(analyzer *troubleshootv1beta2.DeploymentStatus
 	excludeFiles := []string{}
 	fileNames := make([]string, 0)
 	if analyzer.Namespace != "" {
-		fileNames = append(fileNames, filepath.Join("cluster-resources", "deployments", fmt.Sprintf("%s.json", analyzer.Namespace)))
+		fileNames = append(fileNames, filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_DEPLOYMENTS, fmt.Sprintf("%s.json", analyzer.Namespace)))
 	}
 	for _, ns := range analyzer.Namespaces {
-		fileNames = append(fileNames, filepath.Join("cluster-resources", "deployments", fmt.Sprintf("%s.json", ns)))
+		fileNames = append(fileNames, filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_DEPLOYMENTS, fmt.Sprintf("%s.json", ns)))
 	}
 
 	// no namespace specified, so we need to analyze all deployments
 	if len(fileNames) == 0 {
-		fileNames = append(fileNames, filepath.Join("cluster-resources", "deployments", "*.json"))
+		fileNames = append(fileNames, filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_DEPLOYMENTS, "*.json"))
 	}
 
 	results := []*AnalyzeResult{}
diff --git a/pkg/analyze/distribution.go b/pkg/analyze/distribution.go
index 2312daf5..4959b3c0 100644
--- a/pkg/analyze/distribution.go
+++ b/pkg/analyze/distribution.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/pkg/errors"
 	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/replicatedhq/troubleshoot/pkg/constants"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
@@ -143,9 +144,9 @@ func ParseNodesForProviders(nodes []corev1.Node) (providers, string) {
 
 func analyzeDistribution(analyzer *troubleshootv1beta2.Distribution, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
 	var unknownDistribution string
-	collected, err := getCollectedFileContents("cluster-resources/nodes.json")
+	collected, err := getCollectedFileContents(fmt.Sprintf("%s/%s.json", constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_NODES))
 	if err != nil {
-		return nil, errors.Wrap(err, "failed to get contents of nodes.json")
+		return nil, errors.Wrap(err, fmt.Sprintf("failed to get contents of %s.json", constants.CLUSTER_RESOURCES_NODES))
 	}
 
 	var nodes corev1.NodeList
@@ -155,7 +156,7 @@ func analyzeDistribution(analyzer *troubleshootv1beta2.Distribution, getCollecte
 
 	foundProviders, _ := ParseNodesForProviders(nodes.Items)
 
-	apiResourcesBytes, err := getCollectedFileContents("cluster-resources/resources.json")
+	apiResourcesBytes, err := getCollectedFileContents(fmt.Sprintf("%s/%s.json", constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_RESOURCES))
 	// if the file is not found, that is not a fatal error
 	// troubleshoot 0.9.15 and earlier did not collect that file
 	if err == nil {
diff --git a/pkg/analyze/host_analyzer.go b/pkg/analyze/host_analyzer.go
index 6b04b153..0f2f05c0 100644
--- a/pkg/analyze/host_analyzer.go
+++ b/pkg/analyze/host_analyzer.go
@@ -22,6 +22,8 @@ func GetHostAnalyzer(analyzer *troubleshootv1beta2.HostAnalyze) (HostAnalyzer, b
 		return &AnalyzeHostDiskUsage{analyzer.DiskUsage}, true
 	case analyzer.TCPPortStatus != nil:
 		return &AnalyzeHostTCPPortStatus{analyzer.TCPPortStatus}, true
+	case analyzer.UDPPortStatus != nil:
+		return &AnalyzeHostUDPPortStatus{analyzer.UDPPortStatus}, true
 	case analyzer.HTTP != nil:
 		return &AnalyzeHostHTTP{analyzer.HTTP}, true
 	case analyzer.Time != nil:
diff --git a/pkg/analyze/host_udpportstatus.go b/pkg/analyze/host_udpportstatus.go
new file mode 100644
index 00000000..e81baf8a
--- /dev/null
+++ b/pkg/analyze/host_udpportstatus.go
@@ -0,0 +1,100 @@
+package analyzer
+
+import (
+	"encoding/json"
+	"fmt"
+	"path"
+
+	"github.com/pkg/errors"
+	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/replicatedhq/troubleshoot/pkg/collect"
+)
+
+// AnalyzeHostUDPPortStatus is an analyzer that will return only one matching result, or a warning if nothing matches. The first
+// match that is encountered is the one that is returned.
+type AnalyzeHostUDPPortStatus struct {
+	hostAnalyzer *troubleshootv1beta2.UDPPortStatusAnalyze
+}
+
+func (a *AnalyzeHostUDPPortStatus) Title() string {
+	return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "UDP Port Status")
+}
+
+func (a *AnalyzeHostUDPPortStatus) IsExcluded() (bool, error) {
+	return isExcluded(a.hostAnalyzer.Exclude)
+}
+
+func (a *AnalyzeHostUDPPortStatus) Analyze(getCollectedFileContents func(string) ([]byte, error)) ([]*AnalyzeResult, error) {
+	hostAnalyzer := a.hostAnalyzer
+
+	fullPath := path.Join("host-collectors/udpPortStatus", "udpPortStatus.json")
+	if hostAnalyzer.CollectorName != "" {
+		fullPath = path.Join("host-collectors/udpPortStatus", fmt.Sprintf("%s.json", hostAnalyzer.CollectorName))
+	}
+
+	collected, err := getCollectedFileContents(fullPath)
+	if err != nil {
+		return nil, errors.Wrapf(err, "failed to read collected file name: %s", fullPath)
+	}
+	actual := collect.NetworkStatusResult{}
+	if err := json.Unmarshal(collected, &actual); err != nil {
+		return nil, errors.Wrap(err, "failed to unmarshal collected")
+	}
+
+	result := &AnalyzeResult{Title: a.Title()}
+
+	for _, outcome := range hostAnalyzer.Outcomes {
+
+		if outcome.Fail != nil {
+			if outcome.Fail.When == "" {
+				result.IsFail = true
+				result.Message = outcome.Fail.Message
+				result.URI = outcome.Fail.URI
+
+				return []*AnalyzeResult{result}, nil
+			}
+
+			if string(actual.Status) == outcome.Fail.When {
+				result.IsFail = true
+				result.Message = outcome.Fail.Message
+				result.URI = outcome.Fail.URI
+
+				return []*AnalyzeResult{result}, nil
+			}
+		} else if outcome.Warn != nil {
+			if outcome.Warn.When == "" {
+				result.IsWarn = true
+				result.Message = outcome.Warn.Message
+				result.URI = outcome.Warn.URI
+
+				return []*AnalyzeResult{result}, nil
+			}
+
+			if string(actual.Status) == outcome.Warn.When {
+				result.IsWarn = true
+				result.Message = outcome.Warn.Message
+				result.URI = outcome.Warn.URI
+
+				return []*AnalyzeResult{result}, nil
+			}
+		} else if outcome.Pass != nil {
+			if outcome.Pass.When == "" {
+				result.IsPass = true
+				result.Message = outcome.Pass.Message
+				result.URI = outcome.Pass.URI
+
+				return []*AnalyzeResult{result}, nil
+			}
+
+			if string(actual.Status) == outcome.Pass.When {
+				result.IsPass = true
+				result.Message = outcome.Pass.Message
+				result.URI = outcome.Pass.URI
+
+				return []*AnalyzeResult{result}, nil
+			}
+		}
+	}
+
+	return []*AnalyzeResult{result}, nil
+}
diff --git a/pkg/analyze/image_pull_secret.go b/pkg/analyze/image_pull_secret.go
index 72d286c0..29139394 100644
--- a/pkg/analyze/image_pull_secret.go
+++ b/pkg/analyze/image_pull_secret.go
@@ -6,11 +6,12 @@ import (
 
 	"github.com/pkg/errors"
 	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/replicatedhq/troubleshoot/pkg/constants"
 )
 
 func analyzeImagePullSecret(analyzer *troubleshootv1beta2.ImagePullSecret, getChildCollectedFileContents getChildCollectedFileContents) (*AnalyzeResult, error) {
 	var excludeFiles = []string{}
-	imagePullSecrets, err := getChildCollectedFileContents("cluster-resources/image-pull-secrets", excludeFiles)
+	imagePullSecrets, err := getChildCollectedFileContents(fmt.Sprintf("%s/%s", constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_IMAGE_PULL_SECRETS), excludeFiles)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to get file contents for image pull secrets")
 	}
diff --git a/pkg/analyze/ingress.go b/pkg/analyze/ingress.go
index 8d02f6ad..ea7244ee 100644
--- a/pkg/analyze/ingress.go
+++ b/pkg/analyze/ingress.go
@@ -6,11 +6,12 @@ import (
 	"path/filepath"
 
 	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/replicatedhq/troubleshoot/pkg/constants"
 	extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
 )
 
 func analyzeIngress(analyzer *troubleshootv1beta2.Ingress, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
-	ingressData, err := getCollectedFileContents(filepath.Join("cluster-resources", "ingress", fmt.Sprintf("%s.json", analyzer.Namespace)))
+	ingressData, err := getCollectedFileContents(filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_INGRESS, fmt.Sprintf("%s.json", analyzer.Namespace)))
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/analyze/job_status.go b/pkg/analyze/job_status.go
index 86e2bd77..fdbfc096 100644
--- a/pkg/analyze/job_status.go
+++ b/pkg/analyze/job_status.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/pkg/errors"
 	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/replicatedhq/troubleshoot/pkg/constants"
 	batchv1 "k8s.io/api/batch/v1"
 )
 
@@ -22,7 +23,7 @@ func analyzeJobStatus(analyzer *troubleshootv1beta2.JobStatus, getFileContents g
 
 func analyzeOneJobStatus(analyzer *troubleshootv1beta2.JobStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
 	excludeFiles := []string{}
-	files, err := getFileContents(filepath.Join("cluster-resources", "jobs", fmt.Sprintf("%s.json", analyzer.Namespace)), excludeFiles)
+	files, err := getFileContents(filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_JOBS, fmt.Sprintf("%s.json", analyzer.Namespace)), excludeFiles)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to read collected jobs from namespace")
 	}
@@ -71,15 +72,15 @@ func analyzeOneJobStatus(analyzer *troubleshootv1beta2.JobStatus, getFileContent
 func analyzeAllJobStatuses(analyzer *troubleshootv1beta2.JobStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
 	fileNames := make([]string, 0)
 	if analyzer.Namespace != "" {
-		fileNames = append(fileNames, filepath.Join("cluster-resources", "jobs", fmt.Sprintf("%s.json", analyzer.Namespace)))
+		fileNames = append(fileNames, filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_JOBS, fmt.Sprintf("%s.json", analyzer.Namespace)))
 	}
 	for _, ns := range analyzer.Namespaces {
-		fileNames = append(fileNames, filepath.Join("cluster-resources", "jobs", fmt.Sprintf("%s.json", ns)))
+		fileNames = append(fileNames, filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_JOBS, fmt.Sprintf("%s.json", ns)))
 	}
 
 	// no namespace specified, so we need to analyze all jobs
 	if len(analyzer.Namespaces) == 0 {
-		fileNames = append(fileNames, filepath.Join("cluster-resources", "jobs", "*.json"))
+		fileNames = append(fileNames, filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_JOBS, "*.json"))
 	}
 
 	excludeFiles := []string{}
diff --git a/pkg/analyze/kube_resource.go b/pkg/analyze/kube_resource.go
index c4a8f96b..f9d82f5d 100644
--- a/pkg/analyze/kube_resource.go
+++ b/pkg/analyze/kube_resource.go
@@ -8,27 +8,28 @@ import (
 
 	"github.com/pkg/errors"
 	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/replicatedhq/troubleshoot/pkg/constants"
 	iutils "github.com/replicatedhq/troubleshoot/pkg/interfaceutils"
 	"gopkg.in/yaml.v2"
 )
 
 var Filemap = map[string]string{
-	"Deployment":           "deployments",
-	"StatefulSet":          "statefulsets",
-	"NetworkPolicy":        "network-policy",
-	"Pod":                  "pods",
-	"Ingress":              "ingress",
-	"Service":              "services",
-	"ResourceQuota":        "resource-quotas",
-	"Job":                  "jobs",
-	"PersistentVoumeClaim": "pvcs",
-	"pvc":                  "pvcs",
-	"ReplicaSet":           "replicasets",
-	"Namespace":            "namespaces.json",
-	"PersistentVolume":     "pvs.json",
-	"pv":                   "pvs.json",
-	"Node":                 "nodes.json",
-	"StorageClass":         "storage-classes.json",
+	"Deployment":           constants.CLUSTER_RESOURCES_DEPLOYMENTS,
+	"StatefulSet":          constants.CLUSTER_RESOURCES_STATEFULSETS,
+	"NetworkPolicy":        constants.CLUSTER_RESOURCES_NETWORK_POLICY,
+	"Pod":                  constants.CLUSTER_RESOURCES_PODS,
+	"Ingress":              constants.CLUSTER_RESOURCES_INGRESS,
+	"Service":              constants.CLUSTER_RESOURCES_SERVICES,
+	"ResourceQuota":        constants.CLUSTER_RESOURCES_RESOURCE_QUOTA,
+	"Job":                  constants.CLUSTER_RESOURCES_JOBS,
+	"PersistentVoumeClaim": constants.CLUSTER_RESOURCES_PVCS,
+	"pvc":                  constants.CLUSTER_RESOURCES_PVCS,
+	"ReplicaSet":           constants.CLUSTER_RESOURCES_REPLICASETS,
+	"Namespace":            fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_NAMESPACES),
+	"PersistentVolume":     fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_PVS),
+	"pv":                   fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_PVS),
+	"Node":                 fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_NODES),
+	"StorageClass":         fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_STORAGE_CLASS),
 }
 
 // FindResource locates and returns a kubernetes resource as an interface{} from a support bundle based on some basic selectors
@@ -43,12 +44,12 @@ func FindResource(kind string, clusterScoped bool, namespace string, name string
 		return nil, errors.New("failed to find resource")
 	}
 
-	datapath = filepath.Join("cluster-resources", resourceLocation)
+	datapath = filepath.Join(constants.CLUSTER_RESOURCES_DIR, resourceLocation)
 	if !clusterScoped {
 		if namespace == "" {
 			namespace = "default"
 		}
-		datapath = filepath.Join("cluster-resources", resourceLocation, fmt.Sprintf("%s.json", namespace))
+		datapath = filepath.Join(constants.CLUSTER_RESOURCES_DIR, resourceLocation, fmt.Sprintf("%s.json", namespace))
 	}
 
 	file, err := getFileContents(datapath)
diff --git a/pkg/analyze/node_resources.go b/pkg/analyze/node_resources.go
index a37815d9..ad85d868 100644
--- a/pkg/analyze/node_resources.go
+++ b/pkg/analyze/node_resources.go
@@ -9,12 +9,13 @@ import (
 
 	"github.com/pkg/errors"
 	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/replicatedhq/troubleshoot/pkg/constants"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
 )
 
 func analyzeNodeResources(analyzer *troubleshootv1beta2.NodeResources, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
-	collected, err := getCollectedFileContents("cluster-resources/nodes.json")
+	collected, err := getCollectedFileContents(fmt.Sprintf("%s/%s.json", constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_NODES))
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to get contents of nodes.json")
 	}
diff --git a/pkg/analyze/replicaset_status.go b/pkg/analyze/replicaset_status.go
index fcb583a2..ca5e7d2f 100644
--- a/pkg/analyze/replicaset_status.go
+++ b/pkg/analyze/replicaset_status.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/pkg/errors"
 	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/replicatedhq/troubleshoot/pkg/constants"
 	appsv1 "k8s.io/api/apps/v1"
 	"k8s.io/apimachinery/pkg/labels"
 )
@@ -23,7 +24,7 @@ func analyzeReplicaSetStatus(analyzer *troubleshootv1beta2.ReplicaSetStatus, get
 
 func analyzeOneReplicaSetStatus(analyzer *troubleshootv1beta2.ReplicaSetStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
 	excludeFiles := []string{}
-	files, err := getFileContents(filepath.Join("cluster-resources", "replicasets", fmt.Sprintf("%s.json", analyzer.Namespace)), excludeFiles)
+	files, err := getFileContents(filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_REPLICASETS, fmt.Sprintf("%s.json", analyzer.Namespace)), excludeFiles)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to read collected replicasets from namespace")
 	}
@@ -68,14 +69,14 @@ func analyzeOneReplicaSetStatus(analyzer *troubleshootv1beta2.ReplicaSetStatus,
 func analyzeAllReplicaSetStatuses(analyzer *troubleshootv1beta2.ReplicaSetStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
 	fileNames := make([]string, 0)
 	if analyzer.Namespace != "" {
-		fileNames = append(fileNames, filepath.Join("cluster-resources", "replicasets", fmt.Sprintf("%s.json", analyzer.Namespace)))
+		fileNames = append(fileNames, filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_REPLICASETS, fmt.Sprintf("%s.json", analyzer.Namespace)))
 	}
 	for _, ns := range analyzer.Namespaces {
-		fileNames = append(fileNames, filepath.Join("cluster-resources", "replicasets", fmt.Sprintf("%s.json", ns)))
+		fileNames = append(fileNames, filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_REPLICASETS, fmt.Sprintf("%s.json", ns)))
 	}
 
 	if len(fileNames) == 0 {
-		fileNames = append(fileNames, filepath.Join("cluster-resources", "replicasets", "*.json"))
+		fileNames = append(fileNames, filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_REPLICASETS, "*.json"))
 	}
 
 	results := []*AnalyzeResult{}
diff --git a/pkg/analyze/statefulset_status.go b/pkg/analyze/statefulset_status.go
index 2678aa73..d2836db2 100644
--- a/pkg/analyze/statefulset_status.go
+++ b/pkg/analyze/statefulset_status.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/pkg/errors"
 	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/replicatedhq/troubleshoot/pkg/constants"
 	appsv1 "k8s.io/api/apps/v1"
 )
 
@@ -20,7 +21,7 @@ func analyzeStatefulsetStatus(analyzer *troubleshootv1beta2.StatefulsetStatus, g
 
 func analyzeOneStatefulsetStatus(analyzer *troubleshootv1beta2.StatefulsetStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
 	excludeFiles := []string{}
-	files, err := getFileContents(filepath.Join("cluster-resources", "statefulsets", fmt.Sprintf("%s.json", analyzer.Namespace)), excludeFiles)
+	files, err := getFileContents(filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_STATEFULSETS, fmt.Sprintf("%s.json", analyzer.Namespace)), excludeFiles)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to read collected statefulsets from namespace")
 	}
@@ -69,15 +70,15 @@ func analyzeOneStatefulsetStatus(analyzer *troubleshootv1beta2.StatefulsetStatus
 func analyzeAllStatefulsetStatuses(analyzer *troubleshootv1beta2.StatefulsetStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
 	fileNames := make([]string, 0)
 	if analyzer.Namespace != "" {
-		fileNames = append(fileNames, filepath.Join("cluster-resources", "statefulsets", fmt.Sprintf("%s.json", analyzer.Namespace)))
+		fileNames = append(fileNames, filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_STATEFULSETS, fmt.Sprintf("%s.json", analyzer.Namespace)))
 	}
 	for _, ns := range analyzer.Namespaces {
-		fileNames = append(fileNames, filepath.Join("cluster-resources", "statefulsets", fmt.Sprintf("%s.json", ns)))
+		fileNames = append(fileNames, filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_STATEFULSETS, fmt.Sprintf("%s.json", ns)))
 	}
 
 	// no namespace specified, so we need to analyze all statefulsets
 	if len(fileNames) == 0 {
-		fileNames = append(fileNames, filepath.Join("cluster-resources", "statefulsets", "*.json"))
+		fileNames = append(fileNames, filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_STATEFULSETS, "*.json"))
 	}
 
 	excludeFiles := []string{}
diff --git a/pkg/analyze/storage_class.go b/pkg/analyze/storage_class.go
index 82ba42b1..450c6a67 100644
--- a/pkg/analyze/storage_class.go
+++ b/pkg/analyze/storage_class.go
@@ -5,11 +5,12 @@ import (
 	"fmt"
 
 	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/replicatedhq/troubleshoot/pkg/constants"
 	storagev1beta1 "k8s.io/api/storage/v1beta1"
 )
 
 func analyzeStorageClass(analyzer *troubleshootv1beta2.StorageClass, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
-	storageClassesData, err := getCollectedFileContents("cluster-resources/storage-classes.json")
+	storageClassesData, err := getCollectedFileContents(fmt.Sprintf("%s/%s.json", constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_STORAGE_CLASS))
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go b/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go
index 87bd1464..a97ce918 100644
--- a/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go
+++ b/pkg/apis/troubleshoot/v1beta2/hostanalyzer_shared.go
@@ -30,6 +30,12 @@ type TCPPortStatusAnalyze struct {
 	Outcomes      []*Outcome `json:"outcomes" yaml:"outcomes"`
 }
 
+type UDPPortStatusAnalyze struct {
+	AnalyzeMeta   `json:",inline" yaml:",inline"`
+	CollectorName string     `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
+	Outcomes      []*Outcome `json:"outcomes" yaml:"outcomes"`
+}
+
 type DiskUsageAnalyze struct {
 	AnalyzeMeta   `json:",inline" yaml:",inline"`
 	CollectorName string     `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
@@ -115,6 +121,8 @@ type HostAnalyze struct {
 
 	TCPPortStatus *TCPPortStatusAnalyze `json:"tcpPortStatus,omitempty" yaml:"tcpPortStatus,omitempty"`
 
+	UDPPortStatus *UDPPortStatusAnalyze `json:"udpPortStatus,omitempty" yaml:"udpPortStatus,omitempty"`
+
 	HTTP *HTTPAnalyze `json:"http,omitempty" yaml:"http,omitempty"`
 
 	Time *TimeAnalyze `json:"time,omitempty" yaml:"time,omitempty"`
diff --git a/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go
index fb40dd30..d3ee8cce 100644
--- a/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go
+++ b/pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go
@@ -39,6 +39,12 @@ type TCPPortStatus struct {
 	Port              int    `json:"port"`
 }
 
+type UDPPortStatus struct {
+	HostCollectorMeta `json:",inline" yaml:",inline"`
+	Interface         string `json:"interface,omitempty"`
+	Port              int    `json:"port"`
+}
+
 type Kubernetes struct {
 	HostCollectorMeta `json:",inline" yaml:",inline"`
 }
@@ -162,6 +168,7 @@ type HostCollect struct {
 	TCPLoadBalancer       *TCPLoadBalancer       `json:"tcpLoadBalancer,omitempty" yaml:"tcpLoadBalancer,omitempty"`
 	HTTPLoadBalancer      *HTTPLoadBalancer      `json:"httpLoadBalancer,omitempty" yaml:"httpLoadBalancer,omitempty"`
 	TCPPortStatus         *TCPPortStatus         `json:"tcpPortStatus,omitempty" yaml:"tcpPortStatus,omitempty"`
+	UDPPortStatus         *UDPPortStatus         `json:"udpPortStatus,omitempty" yaml:"udpPortStatus,omitempty"`
 	Kubernetes            *Kubernetes            `json:"kubernetes,omitempty" yaml:"kubernetes,omitempty"`
 	IPV4Interfaces        *IPV4Interfaces        `json:"ipv4Interfaces,omitempty" yaml:"ipv4Interfaces,omitempty"`
 	DiskUsage             *DiskUsage             `json:"diskUsage,omitempty" yaml:"diskUsage,omitempty"`
diff --git a/pkg/apis/troubleshoot/v1beta2/remote_collector_shared.go b/pkg/apis/troubleshoot/v1beta2/remote_collector_shared.go
index 0ada95fb..6fd8f4cb 100644
--- a/pkg/apis/troubleshoot/v1beta2/remote_collector_shared.go
+++ b/pkg/apis/troubleshoot/v1beta2/remote_collector_shared.go
@@ -42,6 +42,12 @@ type RemoteTCPPortStatus struct {
 	Port                int    `json:"port"`
 }
 
+type RemoteUDPPortStatus struct {
+	RemoteCollectorMeta `json:",inline" yaml:",inline"`
+	Interface           string `json:"interface,omitempty"`
+	Port                int    `json:"port"`
+}
+
 type RemoteKubernetes struct {
 	RemoteCollectorMeta `json:",inline" yaml:",inline"`
 }
@@ -141,6 +147,7 @@ type RemoteCollect struct {
 	TCPLoadBalancer       *RemoteTCPLoadBalancer       `json:"tcpLoadBalancer,omitempty" yaml:"tcpLoadBalancer,omitempty"`
 	HTTPLoadBalancer      *RemoteHTTPLoadBalancer      `json:"httpLoadBalancer,omitempty" yaml:"httpLoadBalancer,omitempty"`
 	TCPPortStatus         *RemoteTCPPortStatus         `json:"tcpPortStatus,omitempty" yaml:"tcpPortStatus,omitempty"`
+	UDPPortStatus         *RemoteUDPPortStatus         `json:"udpPortStatus,omitempty" yaml:"udpPortStatus,omitempty"`
 	IPV4Interfaces        *RemoteIPV4Interfaces        `json:"ipv4Interfaces,omitempty" yaml:"ipv4Interfaces,omitempty"`
 	DiskUsage             *RemoteDiskUsage             `json:"diskUsage,omitempty" yaml:"diskUsage,omitempty"`
 	HTTP                  *RemoteHTTP                  `json:"http,omitempty" yaml:"http,omitempty"`
diff --git a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go
index 2f76773a..29b154d1 100644
--- a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go
+++ b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go
@@ -1509,6 +1509,11 @@ func (in *HostAnalyze) DeepCopyInto(out *HostAnalyze) {
 		*out = new(TCPPortStatusAnalyze)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.UDPPortStatus != nil {
+		in, out := &in.UDPPortStatus, &out.UDPPortStatus
+		*out = new(UDPPortStatusAnalyze)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.HTTP != nil {
 		in, out := &in.HTTP, &out.HTTP
 		*out = new(HTTPAnalyze)
@@ -1620,6 +1625,11 @@ func (in *HostCollect) DeepCopyInto(out *HostCollect) {
 		*out = new(TCPPortStatus)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.UDPPortStatus != nil {
+		in, out := &in.UDPPortStatus, &out.UDPPortStatus
+		*out = new(UDPPortStatus)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.Kubernetes != nil {
 		in, out := &in.Kubernetes, &out.Kubernetes
 		*out = new(Kubernetes)
@@ -3101,6 +3111,11 @@ func (in *RemoteCollect) DeepCopyInto(out *RemoteCollect) {
 		*out = new(RemoteTCPPortStatus)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.UDPPortStatus != nil {
+		in, out := &in.UDPPortStatus, &out.UDPPortStatus
+		*out = new(RemoteUDPPortStatus)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.IPV4Interfaces != nil {
 		in, out := &in.IPV4Interfaces, &out.IPV4Interfaces
 		*out = new(RemoteIPV4Interfaces)
@@ -3530,6 +3545,22 @@ func (in *RemoteTime) DeepCopy() *RemoteTime {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RemoteUDPPortStatus) DeepCopyInto(out *RemoteUDPPortStatus) {
+	*out = *in
+	in.RemoteCollectorMeta.DeepCopyInto(&out.RemoteCollectorMeta)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RemoteUDPPortStatus.
+func (in *RemoteUDPPortStatus) DeepCopy() *RemoteUDPPortStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(RemoteUDPPortStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Removals) DeepCopyInto(out *Removals) {
 	*out = *in
@@ -4233,6 +4264,49 @@ func (in *TimeAnalyze) DeepCopy() *TimeAnalyze {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *UDPPortStatus) DeepCopyInto(out *UDPPortStatus) {
+	*out = *in
+	in.HostCollectorMeta.DeepCopyInto(&out.HostCollectorMeta)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UDPPortStatus.
+func (in *UDPPortStatus) DeepCopy() *UDPPortStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(UDPPortStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *UDPPortStatusAnalyze) DeepCopyInto(out *UDPPortStatusAnalyze) {
+	*out = *in
+	in.AnalyzeMeta.DeepCopyInto(&out.AnalyzeMeta)
+	if in.Outcomes != nil {
+		in, out := &in.Outcomes, &out.Outcomes
+		*out = make([]*Outcome, len(*in))
+		for i := range *in {
+			if (*in)[i] != nil {
+				in, out := &(*in)[i], &(*out)[i]
+				*out = new(Outcome)
+				(*in).DeepCopyInto(*out)
+			}
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UDPPortStatusAnalyze.
+func (in *UDPPortStatusAnalyze) DeepCopy() *UDPPortStatusAnalyze {
+	if in == nil {
+		return nil
+	}
+	out := new(UDPPortStatusAnalyze)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *WeaveReportAnalyze) DeepCopyInto(out *WeaveReportAnalyze) {
 	*out = *in
diff --git a/pkg/client/troubleshootclientset/clientset.go b/pkg/client/troubleshootclientset/clientset.go
index cc5a2916..7af52d4d 100644
--- a/pkg/client/troubleshootclientset/clientset.go
+++ b/pkg/client/troubleshootclientset/clientset.go
@@ -19,6 +19,7 @@ package troubleshootclientset
 
 import (
 	"fmt"
+	"net/http"
 
 	troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
 	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2"
@@ -33,8 +34,7 @@ type Interface interface {
 	TroubleshootV1beta2() troubleshootv1beta2.TroubleshootV1beta2Interface
 }
 
-// Clientset contains the clients for groups. Each group has exactly one
-// version included in a Clientset.
+// Clientset contains the clients for groups.
 type Clientset struct {
 	*discovery.DiscoveryClient
 	troubleshootV1beta1 *troubleshootv1beta1.TroubleshootV1beta1Client
@@ -62,26 +62,49 @@ func (c *Clientset) Discovery() discovery.DiscoveryInterface {
 // NewForConfig creates a new Clientset for the given config.
 // If config's RateLimiter is not set and QPS and Burst are acceptable,
 // NewForConfig will generate a rate-limiter in configShallowCopy.
+// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
+// where httpClient was generated with rest.HTTPClientFor(c).
 func NewForConfig(c *rest.Config) (*Clientset, error) {
 	configShallowCopy := *c
+
+	if configShallowCopy.UserAgent == "" {
+		configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent()
+	}
+
+	// share the transport between all clients
+	httpClient, err := rest.HTTPClientFor(&configShallowCopy)
+	if err != nil {
+		return nil, err
+	}
+
+	return NewForConfigAndClient(&configShallowCopy, httpClient)
+}
+
+// NewForConfigAndClient creates a new Clientset for the given config and http client.
+// Note the http client provided takes precedence over the configured transport values.
+// If config's RateLimiter is not set and QPS and Burst are acceptable,
+// NewForConfigAndClient will generate a rate-limiter in configShallowCopy.
+func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) {
+	configShallowCopy := *c
 	if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
 		if configShallowCopy.Burst <= 0 {
 			return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0")
 		}
 		configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
 	}
+
 	var cs Clientset
 	var err error
-	cs.troubleshootV1beta1, err = troubleshootv1beta1.NewForConfig(&configShallowCopy)
+	cs.troubleshootV1beta1, err = troubleshootv1beta1.NewForConfigAndClient(&configShallowCopy, httpClient)
 	if err != nil {
 		return nil, err
 	}
-	cs.troubleshootV1beta2, err = troubleshootv1beta2.NewForConfig(&configShallowCopy)
+	cs.troubleshootV1beta2, err = troubleshootv1beta2.NewForConfigAndClient(&configShallowCopy, httpClient)
 	if err != nil {
 		return nil, err
 	}
 
-	cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy)
+	cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient)
 	if err != nil {
 		return nil, err
 	}
@@ -91,12 +114,11 @@ func NewForConfig(c *rest.Config) (*Clientset, error) {
 // NewForConfigOrDie creates a new Clientset for the given config and
 // panics if there is an error in the config.
 func NewForConfigOrDie(c *rest.Config) *Clientset {
-	var cs Clientset
-	cs.troubleshootV1beta1 = troubleshootv1beta1.NewForConfigOrDie(c)
-	cs.troubleshootV1beta2 = troubleshootv1beta2.NewForConfigOrDie(c)
-
-	cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c)
-	return &cs
+	cs, err := NewForConfig(c)
+	if err != nil {
+		panic(err)
+	}
+	return cs
 }
 
 // New creates a new Clientset for the given RESTClient.
diff --git a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_analyzer.go b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_analyzer.go
index ec566758..2661cb46 100644
--- a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_analyzer.go
+++ b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_analyzer.go
@@ -116,7 +116,7 @@ func (c *FakeAnalyzers) UpdateStatus(ctx context.Context, analyzer *v1beta1.Anal
 // Delete takes name of the analyzer and deletes it. Returns an error if one occurs.
 func (c *FakeAnalyzers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(analyzersResource, c.ns, name), &v1beta1.Analyzer{})
+		Invokes(testing.NewDeleteActionWithOptions(analyzersResource, c.ns, name, opts), &v1beta1.Analyzer{})
 
 	return err
 }
diff --git a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_collector.go b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_collector.go
index 8ea3d5ec..2a43591e 100644
--- a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_collector.go
+++ b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_collector.go
@@ -116,7 +116,7 @@ func (c *FakeCollectors) UpdateStatus(ctx context.Context, collector *v1beta1.Co
 // Delete takes name of the collector and deletes it. Returns an error if one occurs.
 func (c *FakeCollectors) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(collectorsResource, c.ns, name), &v1beta1.Collector{})
+		Invokes(testing.NewDeleteActionWithOptions(collectorsResource, c.ns, name, opts), &v1beta1.Collector{})
 
 	return err
 }
diff --git a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_preflight.go b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_preflight.go
index 9e272470..35ddc41a 100644
--- a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_preflight.go
+++ b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_preflight.go
@@ -116,7 +116,7 @@ func (c *FakePreflights) UpdateStatus(ctx context.Context, preflight *v1beta1.Pr
 // Delete takes name of the preflight and deletes it. Returns an error if one occurs.
 func (c *FakePreflights) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(preflightsResource, c.ns, name), &v1beta1.Preflight{})
+		Invokes(testing.NewDeleteActionWithOptions(preflightsResource, c.ns, name, opts), &v1beta1.Preflight{})
 
 	return err
 }
diff --git a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_redactor.go b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_redactor.go
index 3109c798..261d5dd4 100644
--- a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_redactor.go
+++ b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_redactor.go
@@ -116,7 +116,7 @@ func (c *FakeRedactors) UpdateStatus(ctx context.Context, redactor *v1beta1.Reda
 // Delete takes name of the redactor and deletes it. Returns an error if one occurs.
 func (c *FakeRedactors) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(redactorsResource, c.ns, name), &v1beta1.Redactor{})
+		Invokes(testing.NewDeleteActionWithOptions(redactorsResource, c.ns, name, opts), &v1beta1.Redactor{})
 
 	return err
 }
diff --git a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_supportbundle.go b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_supportbundle.go
index 6b4b8e49..46d6b3a2 100644
--- a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_supportbundle.go
+++ b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake/fake_supportbundle.go
@@ -116,7 +116,7 @@ func (c *FakeSupportBundles) UpdateStatus(ctx context.Context, supportBundle *v1
 // Delete takes name of the supportBundle and deletes it. Returns an error if one occurs.
 func (c *FakeSupportBundles) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(supportbundlesResource, c.ns, name), &v1beta1.SupportBundle{})
+		Invokes(testing.NewDeleteActionWithOptions(supportbundlesResource, c.ns, name, opts), &v1beta1.SupportBundle{})
 
 	return err
 }
diff --git a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/troubleshoot_client.go b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/troubleshoot_client.go
index 8b5b3299..628b5ebc 100644
--- a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/troubleshoot_client.go
+++ b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/troubleshoot_client.go
@@ -18,6 +18,8 @@ limitations under the License.
 package v1beta1
 
 import (
+	"net/http"
+
 	v1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
 	"github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
 	rest "k8s.io/client-go/rest"
@@ -58,12 +60,28 @@ func (c *TroubleshootV1beta1Client) SupportBundles(namespace string) SupportBund
 }
 
 // NewForConfig creates a new TroubleshootV1beta1Client for the given config.
+// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
+// where httpClient was generated with rest.HTTPClientFor(c).
 func NewForConfig(c *rest.Config) (*TroubleshootV1beta1Client, error) {
 	config := *c
 	if err := setConfigDefaults(&config); err != nil {
 		return nil, err
 	}
-	client, err := rest.RESTClientFor(&config)
+	httpClient, err := rest.HTTPClientFor(&config)
+	if err != nil {
+		return nil, err
+	}
+	return NewForConfigAndClient(&config, httpClient)
+}
+
+// NewForConfigAndClient creates a new TroubleshootV1beta1Client for the given config and http client.
+// Note the http client provided takes precedence over the configured transport values.
+func NewForConfigAndClient(c *rest.Config, h *http.Client) (*TroubleshootV1beta1Client, error) {
+	config := *c
+	if err := setConfigDefaults(&config); err != nil {
+		return nil, err
+	}
+	client, err := rest.RESTClientForConfigAndClient(&config, h)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_analyzer.go b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_analyzer.go
index b17f4584..c15c4745 100644
--- a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_analyzer.go
+++ b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_analyzer.go
@@ -116,7 +116,7 @@ func (c *FakeAnalyzers) UpdateStatus(ctx context.Context, analyzer *v1beta2.Anal
 // Delete takes name of the analyzer and deletes it. Returns an error if one occurs.
 func (c *FakeAnalyzers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(analyzersResource, c.ns, name), &v1beta2.Analyzer{})
+		Invokes(testing.NewDeleteActionWithOptions(analyzersResource, c.ns, name, opts), &v1beta2.Analyzer{})
 
 	return err
 }
diff --git a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_collector.go b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_collector.go
index d352dd09..1f9c0053 100644
--- a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_collector.go
+++ b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_collector.go
@@ -116,7 +116,7 @@ func (c *FakeCollectors) UpdateStatus(ctx context.Context, collector *v1beta2.Co
 // Delete takes name of the collector and deletes it. Returns an error if one occurs.
 func (c *FakeCollectors) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(collectorsResource, c.ns, name), &v1beta2.Collector{})
+		Invokes(testing.NewDeleteActionWithOptions(collectorsResource, c.ns, name, opts), &v1beta2.Collector{})
 
 	return err
 }
diff --git a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_hostcollector.go b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_hostcollector.go
index 837c7576..cd9d1b6a 100644
--- a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_hostcollector.go
+++ b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_hostcollector.go
@@ -116,7 +116,7 @@ func (c *FakeHostCollectors) UpdateStatus(ctx context.Context, hostCollector *v1
 // Delete takes name of the hostCollector and deletes it. Returns an error if one occurs.
 func (c *FakeHostCollectors) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(hostcollectorsResource, c.ns, name), &v1beta2.HostCollector{})
+		Invokes(testing.NewDeleteActionWithOptions(hostcollectorsResource, c.ns, name, opts), &v1beta2.HostCollector{})
 
 	return err
 }
diff --git a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_hostpreflight.go b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_hostpreflight.go
index 761fd6d8..57e5b907 100644
--- a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_hostpreflight.go
+++ b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_hostpreflight.go
@@ -116,7 +116,7 @@ func (c *FakeHostPreflights) UpdateStatus(ctx context.Context, hostPreflight *v1
 // Delete takes name of the hostPreflight and deletes it. Returns an error if one occurs.
 func (c *FakeHostPreflights) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(hostpreflightsResource, c.ns, name), &v1beta2.HostPreflight{})
+		Invokes(testing.NewDeleteActionWithOptions(hostpreflightsResource, c.ns, name, opts), &v1beta2.HostPreflight{})
 
 	return err
 }
diff --git a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_preflight.go b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_preflight.go
index 67ee4666..a4014d75 100644
--- a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_preflight.go
+++ b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_preflight.go
@@ -116,7 +116,7 @@ func (c *FakePreflights) UpdateStatus(ctx context.Context, preflight *v1beta2.Pr
 // Delete takes name of the preflight and deletes it. Returns an error if one occurs.
 func (c *FakePreflights) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(preflightsResource, c.ns, name), &v1beta2.Preflight{})
+		Invokes(testing.NewDeleteActionWithOptions(preflightsResource, c.ns, name, opts), &v1beta2.Preflight{})
 
 	return err
 }
diff --git a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_redactor.go b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_redactor.go
index 43349660..3bfbf5fa 100644
--- a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_redactor.go
+++ b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_redactor.go
@@ -116,7 +116,7 @@ func (c *FakeRedactors) UpdateStatus(ctx context.Context, redactor *v1beta2.Reda
 // Delete takes name of the redactor and deletes it. Returns an error if one occurs.
 func (c *FakeRedactors) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(redactorsResource, c.ns, name), &v1beta2.Redactor{})
+		Invokes(testing.NewDeleteActionWithOptions(redactorsResource, c.ns, name, opts), &v1beta2.Redactor{})
 
 	return err
 }
diff --git a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_remotecollector.go b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_remotecollector.go
index ae0ddb17..a9bcadf8 100644
--- a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_remotecollector.go
+++ b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_remotecollector.go
@@ -116,7 +116,7 @@ func (c *FakeRemoteCollectors) UpdateStatus(ctx context.Context, remoteCollector
 // Delete takes name of the remoteCollector and deletes it. Returns an error if one occurs.
 func (c *FakeRemoteCollectors) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(remotecollectorsResource, c.ns, name), &v1beta2.RemoteCollector{})
+		Invokes(testing.NewDeleteActionWithOptions(remotecollectorsResource, c.ns, name, opts), &v1beta2.RemoteCollector{})
 
 	return err
 }
diff --git a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_supportbundle.go b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_supportbundle.go
index e5cb8ad8..e71c0cfa 100644
--- a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_supportbundle.go
+++ b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake/fake_supportbundle.go
@@ -116,7 +116,7 @@ func (c *FakeSupportBundles) UpdateStatus(ctx context.Context, supportBundle *v1
 // Delete takes name of the supportBundle and deletes it. Returns an error if one occurs.
 func (c *FakeSupportBundles) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(supportbundlesResource, c.ns, name), &v1beta2.SupportBundle{})
+		Invokes(testing.NewDeleteActionWithOptions(supportbundlesResource, c.ns, name, opts), &v1beta2.SupportBundle{})
 
 	return err
 }
diff --git a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/troubleshoot_client.go b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/troubleshoot_client.go
index 6c3a0fea..ae137af9 100644
--- a/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/troubleshoot_client.go
+++ b/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/troubleshoot_client.go
@@ -18,6 +18,8 @@ limitations under the License.
 package v1beta2
 
 import (
+	"net/http"
+
 	v1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
 	"github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
 	rest "k8s.io/client-go/rest"
@@ -73,12 +75,28 @@ func (c *TroubleshootV1beta2Client) SupportBundles(namespace string) SupportBund
 }
 
 // NewForConfig creates a new TroubleshootV1beta2Client for the given config.
+// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
+// where httpClient was generated with rest.HTTPClientFor(c).
 func NewForConfig(c *rest.Config) (*TroubleshootV1beta2Client, error) {
 	config := *c
 	if err := setConfigDefaults(&config); err != nil {
 		return nil, err
 	}
-	client, err := rest.RESTClientFor(&config)
+	httpClient, err := rest.HTTPClientFor(&config)
+	if err != nil {
+		return nil, err
+	}
+	return NewForConfigAndClient(&config, httpClient)
+}
+
+// NewForConfigAndClient creates a new TroubleshootV1beta2Client for the given config and http client.
+// Note the http client provided takes precedence over the configured transport values.
+func NewForConfigAndClient(c *rest.Config, h *http.Client) (*TroubleshootV1beta2Client, error) {
+	config := *c
+	if err := setConfigDefaults(&config); err != nil {
+		return nil, err
+	}
+	client, err := rest.RESTClientForConfigAndClient(&config, h)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/collect/cluster_resources.go b/pkg/collect/cluster_resources.go
index bed27877..1e7c17a3 100644
--- a/pkg/collect/cluster_resources.go
+++ b/pkg/collect/cluster_resources.go
@@ -12,6 +12,7 @@ import (
 	"strings"
 
 	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/replicatedhq/troubleshoot/pkg/constants"
 	"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
 	"gopkg.in/yaml.v2"
 	authorizationv1 "k8s.io/api/authorization/v1"
@@ -118,17 +119,17 @@ func (c *CollectClusterResources) Collect(progressChan chan<- interface{}) (Coll
 	if len(c.Collector.Namespaces) > 0 {
 		namespaces, namespaceErrors := getNamespaces(ctx, client, c.Collector.Namespaces)
 		namespaceNames = c.Collector.Namespaces
-		output.SaveResult(c.BundlePath, "cluster-resources/namespaces.json", bytes.NewBuffer(namespaces))
-		output.SaveResult(c.BundlePath, "cluster-resources/namespaces-errors.json", marshalErrors(namespaceErrors))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_NAMESPACES)), bytes.NewBuffer(namespaces))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_NAMESPACES)), marshalErrors(namespaceErrors))
 	} else if c.Namespace != "" {
 		namespace, namespaceErrors := getNamespace(ctx, client, c.Namespace)
-		output.SaveResult(c.BundlePath, "cluster-resources/namespaces.json", bytes.NewBuffer(namespace))
-		output.SaveResult(c.BundlePath, "cluster-resources/namespaces-errors.json", marshalErrors(namespaceErrors))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_NAMESPACES)), bytes.NewBuffer(namespace))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_NAMESPACES)), marshalErrors(namespaceErrors))
 		namespaceNames = append(namespaceNames, c.Namespace)
 	} else {
 		namespaces, namespaceList, namespaceErrors := getAllNamespaces(ctx, client)
-		output.SaveResult(c.BundlePath, "cluster-resources/namespaces.json", bytes.NewBuffer(namespaces))
-		output.SaveResult(c.BundlePath, "cluster-resources/namespaces-errors.json", marshalErrors(namespaceErrors))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_NAMESPACES)), bytes.NewBuffer(namespaces))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_NAMESPACES)), marshalErrors(namespaceErrors))
 		if namespaceList != nil {
 			for _, namespace := range namespaceList.Items {
 				namespaceNames = append(namespaceNames, namespace.Name)
@@ -142,9 +143,9 @@ func (c *CollectClusterResources) Collect(progressChan chan<- interface{}) (Coll
 	// auth cani
 	authCanI := authCanI(reviewStatuses, namespaceNames)
 	for k, v := range authCanI {
-		output.SaveResult(c.BundlePath, path.Join("cluster-resources/auth-cani-list", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_AUTH_CANI, k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/auth-cani-list-errors.json", marshalErrors(reviewStatusErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_AUTH_CANI)), marshalErrors(reviewStatusErrors))
 
 	if nsListedFromCluster && !c.Collector.IgnoreRBAC {
 		filteredNamespaces := []string{}
@@ -160,9 +161,9 @@ func (c *CollectClusterResources) Collect(progressChan chan<- interface{}) (Coll
 	// pods
 	pods, podErrors, unhealthyPods := pods(ctx, client, namespaceNames)
 	for k, v := range pods {
-		output.SaveResult(c.BundlePath, path.Join("cluster-resources/pods", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_PODS, k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/pods-errors.json", marshalErrors(podErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_PODS)), marshalErrors(podErrors))
 
 	for _, pod := range unhealthyPods {
 		allContainers := append(pod.Spec.InitContainers, pod.Spec.Containers...)
@@ -176,7 +177,7 @@ func (c *CollectClusterResources) Collect(progressChan chan<- interface{}) (Coll
 			}
 			podLogs, err := savePodLogs(ctx, c.BundlePath, client, &pod, "", container.Name, limits, false, false)
 			if err != nil {
-				errPath := filepath.Join("cluster-resources", "pods", "logs", pod.Namespace, pod.Name, fmt.Sprintf("%s-logs-errors.log", container.Name))
+				errPath := filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_PODS_LOGS, pod.Namespace, pod.Name, fmt.Sprintf("%s-logs-errors.log", container.Name))
 				output.SaveResult(c.BundlePath, errPath, bytes.NewBuffer([]byte(err.Error())))
 			}
 			// Add logs collector results to the rest of the output
@@ -188,156 +189,156 @@ func (c *CollectClusterResources) Collect(progressChan chan<- interface{}) (Coll
 
 	PodDisruptionBudgets, pdbError := getPodDisruptionBudgets(ctx, client, namespaceNames)
 	for k, v := range PodDisruptionBudgets {
-		output.SaveResult(c.BundlePath, path.Join("cluster-resources/pod-disruption-budgets", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_POD_DISRUPTION_BUDGETS, k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/pod-disruption-budgets-info.json", marshalErrors(pdbError))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_POD_DISRUPTION_BUDGETS)), marshalErrors(pdbError))
 
 	// services
 	services, servicesErrors := services(ctx, client, namespaceNames)
 	for k, v := range services {
-		output.SaveResult(c.BundlePath, path.Join("cluster-resources/services", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_SERVICES, k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/services-errors.json", marshalErrors(servicesErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_SERVICES)), marshalErrors(servicesErrors))
 
 	// deployments
 	deployments, deploymentsErrors := deployments(ctx, client, namespaceNames)
 	for k, v := range deployments {
-		output.SaveResult(c.BundlePath, path.Join("cluster-resources/deployments", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_DEPLOYMENTS, k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/deployments-errors.json", marshalErrors(deploymentsErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_DEPLOYMENTS)), marshalErrors(deploymentsErrors))
 
 	// statefulsets
 	statefulsets, statefulsetsErrors := statefulsets(ctx, client, namespaceNames)
 	for k, v := range statefulsets {
-		output.SaveResult(c.BundlePath, path.Join("cluster-resources/statefulsets", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_STATEFULSETS, k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/statefulsets-errors.json", marshalErrors(statefulsetsErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_STATEFULSETS)), marshalErrors(statefulsetsErrors))
 
 	// replicasets
 	replicasets, replicasetsErrors := replicasets(ctx, client, namespaceNames)
 	for k, v := range replicasets {
-		output.SaveResult(c.BundlePath, path.Join("cluster-resources/replicasets", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_STATEFULSETS), k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/replicasets-errors.json", marshalErrors(replicasetsErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_REPLICASETS)), marshalErrors(replicasetsErrors))
 
 	// jobs
 	jobs, jobsErrors := jobs(ctx, client, namespaceNames)
 	for k, v := range jobs {
-		output.SaveResult(c.BundlePath, path.Join("cluster-resources/jobs", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_JOBS, k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/jobs-errors.json", marshalErrors(jobsErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_JOBS)), marshalErrors(jobsErrors))
 
 	// cronJobs
 	cronJobs, cronJobsErrors := cronJobs(ctx, client, namespaceNames)
 	for k, v := range cronJobs {
-		output.SaveResult(c.BundlePath, path.Join("cluster-resources/cronjobs", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_CRONJOBS, k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/cronjobs-errors.json", marshalErrors(cronJobsErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_CRONJOBS)), marshalErrors(cronJobsErrors))
 
 	// ingress
 	ingress, ingressErrors := ingress(ctx, client, namespaceNames)
 	for k, v := range ingress {
-		output.SaveResult(c.BundlePath, path.Join("cluster-resources/ingress", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_INGRESS, k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/ingress-errors.json", marshalErrors(ingressErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_INGRESS)), marshalErrors(ingressErrors))
 
 	// network policy
 	networkPolicy, networkPolicyErrors := networkPolicy(ctx, client, namespaceNames)
 	for k, v := range networkPolicy {
-		output.SaveResult(c.BundlePath, path.Join("cluster-resources/network-policy", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_NETWORK_POLICY, k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/network-policy-errors.json", marshalErrors(networkPolicyErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_NETWORK_POLICY)), marshalErrors(networkPolicyErrors))
 
 	// resource quotas
 	resourceQuota, resourceQuotaErrors := resourceQuota(ctx, client, namespaceNames)
 	for k, v := range resourceQuota {
-		output.SaveResult(c.BundlePath, path.Join("cluster-resources/resource-quotas", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_RESOURCE_QUOTA, k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/resource-quota-errors.json", marshalErrors(resourceQuotaErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_RESOURCE_QUOTA)), marshalErrors(resourceQuotaErrors))
 
 	// storage classes
 	storageClasses, storageErrors := storageClasses(ctx, client)
-	output.SaveResult(c.BundlePath, "cluster-resources/storage-classes.json", bytes.NewBuffer(storageClasses))
-	output.SaveResult(c.BundlePath, "cluster-resources/storage-errors.json", marshalErrors(storageErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_STORAGE_CLASS)), bytes.NewBuffer(storageClasses))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_STORAGE_CLASS)), marshalErrors(storageErrors))
 
 	// crds
 	customResourceDefinitions, crdErrors := crds(ctx, client, c.ClientConfig)
-	output.SaveResult(c.BundlePath, "cluster-resources/custom-resource-definitions.json", bytes.NewBuffer(customResourceDefinitions))
-	output.SaveResult(c.BundlePath, "cluster-resources/custom-resource-definitions-errors.json", marshalErrors(crdErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_CUSTOM_RESOURCE_DEFINITIONS)), bytes.NewBuffer(customResourceDefinitions))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_CUSTOM_RESOURCE_DEFINITIONS)), marshalErrors(crdErrors))
 
 	// crs
 	customResources, crErrors := crs(ctx, dynamicClient, client, c.ClientConfig, namespaceNames)
 	for k, v := range customResources {
-		output.SaveResult(c.BundlePath, fmt.Sprintf("cluster-resources/custom-resources/%v", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_CUSTOM_RESOURCE_DEFINITIONS, k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/custom-resources/custom-resources-errors.json", marshalErrors(crErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_CUSTOM_RESOURCE_DEFINITIONS, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_CUSTOM_RESOURCE_DEFINITIONS)), marshalErrors(crErrors))
 
 	// imagepullsecrets
 	imagePullSecrets, pullSecretsErrors := imagePullSecrets(ctx, client, namespaceNames)
 	for k, v := range imagePullSecrets {
-		output.SaveResult(c.BundlePath, path.Join("cluster-resources/image-pull-secrets", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_IMAGE_PULL_SECRETS, k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/image-pull-secrets-errors.json", marshalErrors(pullSecretsErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_IMAGE_PULL_SECRETS)), marshalErrors(pullSecretsErrors))
 
 	// nodes
 	nodes, nodeErrors := nodes(ctx, client)
-	output.SaveResult(c.BundlePath, "cluster-resources/nodes.json", bytes.NewBuffer(nodes))
-	output.SaveResult(c.BundlePath, "cluster-resources/nodes-errors.json", marshalErrors(nodeErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_NODES)), bytes.NewBuffer(nodes))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_NODES)), marshalErrors(nodeErrors))
 
 	groups, resources, groupsResourcesErrors := apiResources(ctx, client)
-	output.SaveResult(c.BundlePath, "cluster-resources/groups.json", bytes.NewBuffer(groups))
-	output.SaveResult(c.BundlePath, "cluster-resources/resources.json", bytes.NewBuffer(resources))
-	output.SaveResult(c.BundlePath, "cluster-resources/groups-resources-errors.json", marshalErrors(groupsResourcesErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_GROUPS)), bytes.NewBuffer(groups))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_RESOURCES)), bytes.NewBuffer(resources))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-%s-errors.json", constants.CLUSTER_RESOURCES_GROUPS, constants.CLUSTER_RESOURCES_RESOURCES)), marshalErrors(groupsResourcesErrors))
 
 	// limit ranges
 	limitRanges, limitRangesErrors := limitRanges(ctx, client, namespaceNames)
 	for k, v := range limitRanges {
-		output.SaveResult(c.BundlePath, path.Join("cluster-resources/limitranges", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_LIMITRANGES, k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/limitranges-errors.json", marshalErrors(limitRangesErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_LIMITRANGES)), marshalErrors(limitRangesErrors))
 
 	//Events
 	events, eventsErrors := events(ctx, client, namespaceNames)
 	for k, v := range events {
-		output.SaveResult(c.BundlePath, path.Join("cluster-resources/events", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_EVENTS, k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/events-errors.json", marshalErrors(eventsErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_EVENTS)), marshalErrors(eventsErrors))
 
 	//Persistent Volumes
 	pvs, pvsErrors := pvs(ctx, client)
-	output.SaveResult(c.BundlePath, "cluster-resources/pvs.json", bytes.NewBuffer(pvs))
-	output.SaveResult(c.BundlePath, "cluster-resources/pvs-errors.json", marshalErrors(pvsErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_PVS), bytes.NewBuffer(pvs))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_PVS)), marshalErrors(pvsErrors))
 
 	//Persistent Volume Claims
 	pvcs, pvcsErrors := pvcs(ctx, client, namespaceNames)
 	for k, v := range pvcs {
-		output.SaveResult(c.BundlePath, path.Join("cluster-resources/pvcs", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_PVCS, k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/pvcs-errors.json", marshalErrors(pvcsErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_PVCS)), marshalErrors(pvcsErrors))
 
 	//Roles
 	roles, rolesErrors := roles(ctx, client, namespaceNames)
 	for k, v := range roles {
-		output.SaveResult(c.BundlePath, path.Join("cluster-resources/roles", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_ROLES, k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/roles-errors.json", marshalErrors(rolesErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_ROLES)), marshalErrors(rolesErrors))
 
 	//Role Bindings
 	roleBindings, roleBindingsErrors := roleBindings(ctx, client, namespaceNames)
 	for k, v := range roleBindings {
-		output.SaveResult(c.BundlePath, path.Join("cluster-resources/rolebindings", k), bytes.NewBuffer(v))
+		output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_ROLE_BINDINGS, k), bytes.NewBuffer(v))
 	}
-	output.SaveResult(c.BundlePath, "cluster-resources/rolebindings-errors.json", marshalErrors(roleBindingsErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_ROLE_BINDINGS)), marshalErrors(roleBindingsErrors))
 
 	//Cluster Roles
 	clusterRoles, clusterRolesErrors := clusterRoles(ctx, client)
-	output.SaveResult(c.BundlePath, "cluster-resources/clusterroles.json", bytes.NewBuffer(clusterRoles))
-	output.SaveResult(c.BundlePath, "cluster-resources/clusterroles-errors.json", marshalErrors(clusterRolesErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_CLUSTER_ROLES), bytes.NewBuffer(clusterRoles))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_CLUSTER_ROLES)), marshalErrors(clusterRolesErrors))
 
 	//Cluster Role Bindings
 	clusterRoleBindings, clusterRoleBindingsErrors := clusterRoleBindings(ctx, client)
-	output.SaveResult(c.BundlePath, "cluster-resources/clusterRoleBindings.json", bytes.NewBuffer(clusterRoleBindings))
-	output.SaveResult(c.BundlePath, "cluster-resources/clusterRoleBindings-errors.json", marshalErrors(clusterRoleBindingsErrors))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_CLUSTER_ROLE_BINDINGS), bytes.NewBuffer(clusterRoleBindings))
+	output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_CLUSTER_ROLE_BINDINGS)), marshalErrors(clusterRoleBindingsErrors))
 
 	return output, nil
 }
diff --git a/pkg/collect/host_collector.go b/pkg/collect/host_collector.go
index d7ba5749..9e538922 100644
--- a/pkg/collect/host_collector.go
+++ b/pkg/collect/host_collector.go
@@ -24,6 +24,8 @@ func GetHostCollector(collector *troubleshootv1beta2.HostCollect, bundlePath str
 		return &CollectHostDiskUsage{collector.DiskUsage, bundlePath}, true
 	case collector.TCPPortStatus != nil:
 		return &CollectHostTCPPortStatus{collector.TCPPortStatus, bundlePath}, true
+	case collector.UDPPortStatus != nil:
+		return &CollectHostUDPPortStatus{collector.UDPPortStatus, bundlePath}, true
 	case collector.HTTP != nil:
 		return &CollectHostHTTP{collector.HTTP, bundlePath}, true
 	case collector.Time != nil:
diff --git a/pkg/collect/host_udpportstatus.go b/pkg/collect/host_udpportstatus.go
new file mode 100644
index 00000000..f190fe41
--- /dev/null
+++ b/pkg/collect/host_udpportstatus.go
@@ -0,0 +1,78 @@
+package collect
+
+import (
+	"bytes"
+	"encoding/json"
+	"net"
+	"path/filepath"
+	"strings"
+
+	"github.com/pkg/errors"
+	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+)
+
+type CollectHostUDPPortStatus struct {
+	hostCollector *troubleshootv1beta2.UDPPortStatus
+	BundlePath    string
+}
+
+func (c *CollectHostUDPPortStatus) Title() string {
+	return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "UDP Port Status")
+}
+
+func (c *CollectHostUDPPortStatus) IsExcluded() (bool, error) {
+	return isExcluded(c.hostCollector.Exclude)
+}
+
+func (c *CollectHostUDPPortStatus) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
+	listenAddress := net.UDPAddr{
+		IP:   net.ParseIP("0.0.0.0"),
+		Port: c.hostCollector.Port,
+	}
+
+	if c.hostCollector.Interface != "" {
+		iface, err := net.InterfaceByName(c.hostCollector.Interface)
+		if err != nil {
+			return nil, errors.Wrapf(err, "lookup interface %s", c.hostCollector.Interface)
+		}
+		ip, err := getIPv4FromInterface(iface)
+		if err != nil {
+			return nil, errors.Wrapf(err, "get ipv4 address for interface %s", c.hostCollector.Interface)
+		}
+		listenAddress.IP = ip
+	}
+
+	var networkStatus NetworkStatus
+	lstn, err := net.ListenUDP("udp", &listenAddress)
+	if err != nil {
+		if strings.Contains(err.Error(), "address already in use") {
+			networkStatus = NetworkStatusAddressInUse
+		} else {
+			networkStatus = NetworkStatusErrorOther
+		}
+	} else {
+		networkStatus = NetworkStatusConnected
+		lstn.Close()
+	}
+
+	result := NetworkStatusResult{
+		Status: networkStatus,
+	}
+	b, err := json.Marshal(result)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to marshal result")
+	}
+
+	collectorName := c.hostCollector.CollectorName
+	if collectorName == "" {
+		collectorName = "udpPortStatus"
+	}
+	name := filepath.Join("host-collectors/udpPortStatus", collectorName+".json")
+
+	output := NewResult()
+	output.SaveResult(c.BundlePath, name, bytes.NewBuffer(b))
+
+	return map[string][]byte{
+		name: b,
+	}, nil
+}
diff --git a/pkg/collect/logs.go b/pkg/collect/logs.go
index 2b5d60fd..666f42ec 100644
--- a/pkg/collect/logs.go
+++ b/pkg/collect/logs.go
@@ -173,7 +173,7 @@ func savePodLogs(
 	// TODO: Abstract away hard coded directory structure paths
 	// Maybe create a FS provider or something similar
 	filePathPrefix := filepath.Join(
-		"cluster-resources", "pods", "logs", pod.Namespace, pod.Name, pod.Spec.Containers[0].Name,
+		constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_PODS_LOGS, pod.Namespace, pod.Name, pod.Spec.Containers[0].Name,
 	)
 
 	// TODO: If collectorName is empty, the path is stored with a leading slash
@@ -185,7 +185,7 @@ func savePodLogs(
 	if container != "" {
 		linkRelPathPrefix = fmt.Sprintf("%s/%s/%s", collectorName, pod.Name, container)
 		filePathPrefix = filepath.Join(
-			"cluster-resources", "pods", "logs", pod.Namespace, pod.Name, container,
+			constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_PODS_LOGS, pod.Namespace, pod.Name, container,
 		)
 	}
 
diff --git a/pkg/collect/remote_collector.go b/pkg/collect/remote_collector.go
index e2481acf..6db6ab96 100644
--- a/pkg/collect/remote_collector.go
+++ b/pkg/collect/remote_collector.go
@@ -205,6 +205,15 @@ func (c *RemoteCollector) toHostCollector() (*troubleshootv1beta2.HostCollect, e
 			Interface: c.Collect.TCPPortStatus.Interface,
 			Port:      c.Collect.TCPPortStatus.Port,
 		}
+	case c.Collect.UDPPortStatus != nil:
+		hostCollect.UDPPortStatus = &troubleshootv1beta2.UDPPortStatus{
+			HostCollectorMeta: troubleshootv1beta2.HostCollectorMeta{
+				CollectorName: c.Collect.UDPPortStatus.CollectorName,
+				Exclude:       c.Collect.UDPPortStatus.Exclude,
+			},
+			Interface: c.Collect.UDPPortStatus.Interface,
+			Port:      c.Collect.UDPPortStatus.Port,
+		}
 	case c.Collect.HTTP != nil:
 		hostCollect.HTTP = &troubleshootv1beta2.HostHTTP{
 			HostCollectorMeta: troubleshootv1beta2.HostCollectorMeta{
diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go
index 05194e60..0238224d 100644
--- a/pkg/constants/constants.go
+++ b/pkg/constants/constants.go
@@ -13,4 +13,36 @@ const (
 	VersionFilename = "version.yaml"
 	// DEFAULT_LOGS_COLLECTOR_TIMEOUT is the default timeout for logs collector.
 	DEFAULT_LOGS_COLLECTOR_TIMEOUT = 60 * time.Second
+
+	// Cluster Resources Collector Directories
+	CLUSTER_RESOURCES_DIR                         = "cluster-resources"
+	CLUSTER_RESOURCES_NAMESPACES                  = "namespace"
+	CLUSTER_RESOURCES_AUTH_CANI                   = "auth-cani-list"
+	CLUSTER_RESOURCES_PODS                        = "pods"
+	CLUSTER_RESOURCES_PODS_LOGS                   = "pods/logs"
+	CLUSTER_RESOURCES_POD_DISRUPTION_BUDGETS      = "pod-disruption-budgets"
+	CLUSTER_RESOURCES_SERVICES                    = "services"
+	CLUSTER_RESOURCES_DEPLOYMENTS                 = "deployments"
+	CLUSTER_RESOURCES_REPLICASETS                 = "replicasets"
+	CLUSTER_RESOURCES_STATEFULSETS                = "statefulsets"
+	CLUSTER_RESOURCES_JOBS                        = "jobs"
+	CLUSTER_RESOURCES_CRONJOBS                    = "cronjobs"
+	CLUSTER_RESOURCES_INGRESS                     = "ingress"
+	CLUSTER_RESOURCES_NETWORK_POLICY              = "network-policy"
+	CLUSTER_RESOURCES_RESOURCE_QUOTA              = "resource-quota"
+	CLUSTER_RESOURCES_STORAGE_CLASS               = "storage-classes"
+	CLUSTER_RESOURCES_CUSTOM_RESOURCE_DEFINITIONS = "custom-resource-definitions"
+	CLUSTER_RESOURCES_CUSTOM_RESOURCES            = "custom-resources"
+	CLUSTER_RESOURCES_IMAGE_PULL_SECRETS          = "image-pull-secrets"
+	CLUSTER_RESOURCES_NODES                       = "nodes"
+	CLUSTER_RESOURCES_GROUPS                      = "groups"
+	CLUSTER_RESOURCES_RESOURCES                   = "resources"
+	CLUSTER_RESOURCES_LIMITRANGES                 = "limitranges"
+	CLUSTER_RESOURCES_EVENTS                      = "events"
+	CLUSTER_RESOURCES_PVS                         = "pvs"
+	CLUSTER_RESOURCES_PVCS                        = "pvcs"
+	CLUSTER_RESOURCES_ROLES                       = "roles"
+	CLUSTER_RESOURCES_ROLE_BINDINGS               = "rolebindings"
+	CLUSTER_RESOURCES_CLUSTER_ROLES               = "clusterroles"
+	CLUSTER_RESOURCES_CLUSTER_ROLE_BINDINGS       = "clusterRoleBindings"
 )
diff --git a/pkg/redact/redact.go b/pkg/redact/redact.go
index 29312e35..920a3756 100644
--- a/pkg/redact/redact.go
+++ b/pkg/redact/redact.go
@@ -10,6 +10,7 @@ import (
 	"github.com/gobwas/glob"
 	"github.com/pkg/errors"
 	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/replicatedhq/troubleshoot/pkg/constants"
 )
 
 const (
@@ -333,7 +334,7 @@ func getRedactors(path string) ([]Redactor, error) {
 
 	uniqueCRs := map[string]bool{}
 	for _, cr := range customResources {
-		fileglob := fmt.Sprintf("cluster-resources/custom-resources/%s/*", cr.resource)
+		fileglob := fmt.Sprintf("%s/%s/%s/*", constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_CUSTOM_RESOURCES, cr.resource)
 		redactors = append(redactors, NewYamlRedactor(cr.yamlPath, fileglob, ""))
 
 		// redact kubectl last applied annotation once for each resource since it contains copies of
diff --git a/pkg/supportbundle/parse.go b/pkg/supportbundle/parse.go
index c5134ae9..e8843351 100644
--- a/pkg/supportbundle/parse.go
+++ b/pkg/supportbundle/parse.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/mholt/archiver/v3"
 	"github.com/pkg/errors"
+	"github.com/replicatedhq/troubleshoot/pkg/constants"
 	types "github.com/replicatedhq/troubleshoot/pkg/supportbundle/types"
 	corev1 "k8s.io/api/core/v1"
 )
@@ -20,15 +21,15 @@ var (
 )
 
 func getPodsFilePath(namespace string) string {
-	return filepath.Join("cluster-resources", "pods", fmt.Sprintf("%s.json", namespace))
+	return filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_PODS, fmt.Sprintf("%s.json", namespace))
 }
 
 func getContainerLogsFilePath(namespace string, podName string, containerName string) string {
-	return filepath.Join("cluster-resources", "pods", "logs", namespace, podName, fmt.Sprintf("%s.log", containerName))
+	return filepath.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_PODS_LOGS, namespace, podName, fmt.Sprintf("%s.log", containerName))
 }
 
 func getEventsFilePath(namespace string) string {
-	return filepath.Join("cluster-resources", "events", fmt.Sprintf("%s.json", namespace))
+	return filepath.Join(constants.CLUSTER_RESOURCES_DIR, "events", fmt.Sprintf("%s.json", namespace))
 }
 
 func GetPodDetails(bundleArchive string, podNamespace string, podName string) (*types.PodDetails, error) {
diff --git a/schemas/analyzer-troubleshoot-v1beta2.json b/schemas/analyzer-troubleshoot-v1beta2.json
index 26c2fab3..87fa17ef 100644
--- a/schemas/analyzer-troubleshoot-v1beta2.json
+++ b/schemas/analyzer-troubleshoot-v1beta2.json
@@ -3512,6 +3512,82 @@
                     "oneOf": [{"type": "string"},{"type": "boolean"}]
                   }
                 }
+              },
+              "udpPortStatus": {
+                "type": "object",
+                "required": [
+                  "outcomes"
+                ],
+                "properties": {
+                  "annotations": {
+                    "type": "object",
+                    "additionalProperties": {
+                      "type": "string"
+                    }
+                  },
+                  "checkName": {
+                    "type": "string"
+                  },
+                  "collectorName": {
+                    "type": "string"
+                  },
+                  "exclude": {
+                    "oneOf": [{"type": "string"},{"type": "boolean"}]
+                  },
+                  "outcomes": {
+                    "type": "array",
+                    "items": {
+                      "type": "object",
+                      "properties": {
+                        "fail": {
+                          "type": "object",
+                          "properties": {
+                            "message": {
+                              "type": "string"
+                            },
+                            "uri": {
+                              "type": "string"
+                            },
+                            "when": {
+                              "type": "string"
+                            }
+                          }
+                        },
+                        "pass": {
+                          "type": "object",
+                          "properties": {
+                            "message": {
+                              "type": "string"
+                            },
+                            "uri": {
+                              "type": "string"
+                            },
+                            "when": {
+                              "type": "string"
+                            }
+                          }
+                        },
+                        "warn": {
+                          "type": "object",
+                          "properties": {
+                            "message": {
+                              "type": "string"
+                            },
+                            "uri": {
+                              "type": "string"
+                            },
+                            "when": {
+                              "type": "string"
+                            }
+                          }
+                        }
+                      }
+                    }
+                  },
+                  "strict": {
+                    "oneOf": [{"type": "string"},{"type": "boolean"}]
+                  }
+                }
               }
             }
           }
diff --git a/schemas/preflight-troubleshoot-v1beta2.json b/schemas/preflight-troubleshoot-v1beta2.json
index d5c174c8..d8a702e8 100644
--- a/schemas/preflight-troubleshoot-v1beta2.json
+++ b/schemas/preflight-troubleshoot-v1beta2.json
@@ -9330,6 +9330,26 @@
                     "oneOf": [{"type": "string"},{"type": "boolean"}]
                   }
                 }
+              },
+              "udpPortStatus": {
+                "type": "object",
+                "required": [
+                  "port"
+                ],
+                "properties": {
+                  "collectorName": {
+                    "type": "string"
+                  },
+                  "exclude": {
+                    "oneOf": [{"type": "string"},{"type": "boolean"}]
+                  },
+                  "interface": {
+                    "type": "string"
+                  },
+                  "port": {
+                    "type": "integer"
+                  }
+                }
               }
             }
           }
diff --git a/schemas/supportbundle-troubleshoot-v1beta2.json b/schemas/supportbundle-troubleshoot-v1beta2.json
index 8e5c1d0a..43407b92 100644
--- a/schemas/supportbundle-troubleshoot-v1beta2.json
+++ b/schemas/supportbundle-troubleshoot-v1beta2.json
@@ -10313,6 +10313,82 @@
                     "oneOf": [{"type": "string"},{"type": "boolean"}]
                   }
                 }
+              },
+              "udpPortStatus": {
+                "type": "object",
+                "required": [
+                  "outcomes"
+                ],
+                "properties": {
+                  "annotations": {
+                    "type": "object",
+                    "additionalProperties": {
+                      "type": "string"
+                    }
+                  },
+                  "checkName": {
+                    "type": "string"
+                  },
+                  "collectorName": {
+                    "type": "string"
+                  },
+                  "exclude": {
+                    "oneOf": [{"type": "string"},{"type": "boolean"}]
+                  },
+                  "outcomes": {
+                    "type": "array",
+                    "items": {
+                      "type": "object",
+                      "properties": {
+                        "fail": {
+                          "type": "object",
+                          "properties": {
+                            "message": {
+                              "type": "string"
+                            },
+                            "uri": {
+                              "type": "string"
+                            },
+                            "when": {
+                              "type": "string"
+                            }
+                          }
+                        },
+                        "pass": {
+                          "type": "object",
+                          "properties": {
+                            "message": {
+                              "type": "string"
+                            },
+                            "uri": {
+                              "type": "string"
+                            },
+                            "when": {
+                              "type": "string"
+                            }
+                          }
+                        },
+                        "warn": {
+                          "type": "object",
+                          "properties": {
+                            "message": {
+                              "type": "string"
+                            },
+                            "uri": {
+                              "type": "string"
+                            },
+                            "when": {
+                              "type": "string"
+                            }
+                          }
+                        }
+                      }
+                    }
+                  },
+                  "strict": {
+                    "oneOf": [{"type": "string"},{"type": "boolean"}]
+                  }
+                }
               }
             }
           }
@@ -10821,6 +10897,26 @@
                     "oneOf": [{"type": "string"},{"type": "boolean"}]
                   }
                 }
+              },
+              "udpPortStatus": {
+                "type": "object",
+                "required": [
+                  "port"
+                ],
+                "properties": {
+                  "collectorName": {
+                    "type": "string"
+                  },
+                  "exclude": {
+                    "oneOf": [{"type": "string"},{"type": "boolean"}]
+                  },
+                  "interface": {
+                    "type": "string"
+                  },
+                  "port": {
+                    "type": "integer"
+                  }
+                }
               }
             }
           }


Test patch:
diff --git a/pkg/analyze/host_udpportstatus_test.go b/pkg/analyze/host_udpportstatus_test.go
new file mode 100644
index 00000000..bfc09794
--- /dev/null
+++ b/pkg/analyze/host_udpportstatus_test.go
@@ -0,0 +1,104 @@
+package analyzer
+
+import (
+	"encoding/json"
+	"testing"
+
+	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/replicatedhq/troubleshoot/pkg/collect"
+	"github.com/stretchr/testify/require"
+)
+
+func TestAnalyzeUDPPortStatus(t *testing.T) {
+	tt := []struct {
+		name      string
+		collected collect.NetworkStatusResult
+		analyzer  troubleshootv1beta2.UDPPortStatusAnalyze
+		results   []*AnalyzeResult
+		wantErr   bool
+	}{
+		{
+			name: "pass expect single result",
+			collected: collect.NetworkStatusResult{
+				Status: "connected",
+			},
+			analyzer: troubleshootv1beta2.UDPPortStatusAnalyze{
+				AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
+					CheckName: "Kubernetes API UDP Port Status",
+				},
+				Outcomes: []*troubleshootv1beta2.Outcome{
+					{
+						Pass: &troubleshootv1beta2.SingleOutcome{
+							When:    "connected",
+							Message: "Port 6443 is open",
+						},
+					},
+					{
+						Warn: &troubleshootv1beta2.SingleOutcome{
+							Message: "Unexpected port status",
+						},
+					},
+				},
+			},
+			results: []*AnalyzeResult{
+				{
+					Title:   "Kubernetes API UDP Port Status",
+					IsPass:  true,
+					Message: "Port 6443 is open",
+				},
+			},
+		},
+
+		{
+			name: "get warn if no match",
+			collected: collect.NetworkStatusResult{
+				Status: "connected",
+			},
+			analyzer: troubleshootv1beta2.UDPPortStatusAnalyze{
+				AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
+					CheckName: "Kubernetes API UDP Port Status",
+				},
+				Outcomes: []*troubleshootv1beta2.Outcome{
+					{
+						Fail: &troubleshootv1beta2.SingleOutcome{
+							When:    "foo",
+							Message: "Port 6443 is open",
+						},
+					},
+					{
+						Warn: &troubleshootv1beta2.SingleOutcome{
+							Message: "Unexpected port status",
+						},
+					},
+				},
+			},
+			results: []*AnalyzeResult{
+				{
+					Title:   "Kubernetes API UDP Port Status",
+					IsWarn:  true,
+					Message: "Unexpected port status",
+				},
+			},
+		},
+	}
+
+	for _, tc := range tt {
+		t.Run(tc.name, func(t *testing.T) {
+			fn := func(_ string) ([]byte, error) {
+				return json.Marshal(&tc.collected)
+			}
+			analyzer := AnalyzeHostUDPPortStatus{
+				hostAnalyzer: &tc.analyzer,
+			}
+			results, err := analyzer.Analyze(fn)
+			if tc.wantErr {
+				require.NotNil(t, err)
+				return
+			}
+
+			require.Nil(t, err)
+			require.Equal(t, tc.results, results)
+		})
+	}
+
+}
diff --git a/pkg/collect/host_udpportstatus_test.go b/pkg/collect/host_udpportstatus_test.go
new file mode 100644
index 00000000..569cbfe0
--- /dev/null
+++ b/pkg/collect/host_udpportstatus_test.go
@@ -0,0 +1,94 @@
+package collect
+
+import (
+	"net"
+	"os"
+	"strconv"
+	"testing"
+
+	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestCollectHostUDPPortStatus_Collect(t *testing.T) {
+	getPort := func() (int, *net.UDPConn, error) {
+		listenAddress := net.UDPAddr{
+			IP: net.ParseIP("0.0.0.0"),
+		}
+		conn, err := net.ListenUDP("udp", &listenAddress)
+		if err != nil {
+			return 0, nil, err
+		}
+
+		_, p, err := net.SplitHostPort(conn.LocalAddr().String())
+		if err != nil {
+			return 0, nil, err
+		}
+		port, err := strconv.Atoi(p)
+		return port, conn, err
+	}
+
+	type fields struct {
+		hostCollector *troubleshootv1beta2.UDPPortStatus
+	}
+	tests := []struct {
+		name    string
+		getPort func(t *testing.T) (port int, closeFn func() error)
+		fields  fields
+		want    map[string][]byte
+	}{
+		{
+			name: "connected",
+			getPort: func(t *testing.T) (int, func() error) {
+				port, conn, err := getPort()
+				require.NoError(t, err)
+				conn.Close()
+				return port, nil
+			},
+			want: map[string][]byte{
+				"host-collectors/udpPortStatus/udpPortStatus.json": []byte(`{"status":"connected","message":""}`),
+			},
+		},
+		{
+			name: "address-in-use",
+			getPort: func(t *testing.T) (int, func() error) {
+				port, conn, err := getPort()
+				require.NoError(t, err)
+				return port, conn.Close
+			},
+			want: map[string][]byte{
+				"host-collectors/udpPortStatus/udpPortStatus.json": []byte(`{"status":"address-in-use","message":""}`),
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			port, closeFn := tt.getPort(t)
+			if closeFn != nil {
+				defer closeFn()
+			}
+
+			tmpDir, err := os.MkdirTemp("", "bundle")
+			require.NoError(t, err)
+
+			c := &CollectHostUDPPortStatus{
+				hostCollector: &troubleshootv1beta2.UDPPortStatus{
+					Port: port,
+				},
+				BundlePath: tmpDir,
+			}
+
+			progresChan := make(chan interface{})
+			defer close(progresChan)
+			go func() {
+				for range progresChan {
+				}
+			}()
+			got, err := c.Collect(progresChan)
+			require.NoError(t, err)
+
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}