Repository: replicatedhq/troubleshoot
Pull request #1063 (2023-03-21 13:23:33)

feat: Add partition type info to block device collector
## Description, Motivation and Context

A minor improvement in the block devices collector. We now collect the partition type information of a block device.

Fixes: #970

<!--- If it relates to an open issue, please link to the issue here.
e.g.
Fixes: #414 
-->

## Checklist

- [x] New and existing tests pass locally with introduced changes.
- [x] Tests for the changes have been added (for bug fixes / features)
- [x] The commit message(s) are informative and highlight any breaking changes
- [ ] Any documentation required has been added/updated. For changes to https://troubleshoot.sh/ create a PR [here](https://github.com/replicatedhq/troubleshoot.sh/pulls)

## Does this PR introduce a breaking change?
- [ ] Yes
- [x] No

<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->



Patch:
diff --git a/README.md b/README.md
index ffae84f2..2b545d1b 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,11 @@ And see our other tool [sbctl](https://github.com/replicatedhq/sbctl) that makes
 
 # Community
 
-For questions about using Troubleshoot, there's a [Replicated Community](https://help.replicated.com/community) forum, and a [#app-troubleshoot channel in Kubernetes Slack](https://kubernetes.slack.com/channels/app-troubleshoot).
+For questions about using Troubleshoot, how to contribute and engaging with the project in any other way, please refer to the following resources and channels.
+
+- [Replicated Community](https://help.replicated.com/community) forum
+- [#app-troubleshoot channel in Kubernetes Slack](https://kubernetes.slack.com/channels/app-troubleshoot)
+- [#Community meetings calendar](https://calendar.google.com/calendar/u/0?cid=Y19mMGx1aGhiZGtscGllOGo5dWpicXMwNnN1a0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t). This happen monthly but dates may change and would be kept upto date in the calendar.
 
 # Software Bill of Materials
 A signed SBOM  that includes Troubleshoot dependencies is included in each release.
diff --git a/cmd/troubleshoot/cli/run.go b/cmd/troubleshoot/cli/run.go
index 8d9244c4..d26d3637 100644
--- a/cmd/troubleshoot/cli/run.go
+++ b/cmd/troubleshoot/cli/run.go
@@ -114,11 +114,6 @@ func runTroubleshoot(v *viper.Viper, arg []string) error {
 			return errors.Wrap(err, "unable to parse selector")
 		}
 
-		namespace := ""
-		if v.GetString("namespace") != "" {
-			namespace = v.GetString("namespace")
-		}
-
 		config, err := k8sutil.GetRESTConfig()
 		if err != nil {
 			return errors.Wrap(err, "failed to convert kube flags to rest config")
@@ -129,6 +124,22 @@ func runTroubleshoot(v *viper.Viper, arg []string) error {
 			return errors.Wrap(err, "failed to convert create k8s client")
 		}
 
+		namespace := ""
+
+		if v.GetString("namespace") != "" {
+			namespace = v.GetString("namespace")
+		} else {
+			IsNamespacedScopeRBAC, err := k8sutil.IsNamespacedScopeRBAC(client)
+			if err != nil {
+				return errors.Wrap(err, "failed to check if cluster is namespaced")
+			}
+
+			if !IsNamespacedScopeRBAC {
+				kubeconfig := k8sutil.GetKubeconfig()
+				namespace, _, _ = kubeconfig.Namespace()
+			}
+		}
+
 		var bundlesFromCluster []string
 
 		// Search cluster for Troubleshoot objects in cluster
diff --git a/config/crds/troubleshoot.sh_analyzers.yaml b/config/crds/troubleshoot.sh_analyzers.yaml
index d5d78e64..fa5b592c 100644
--- a/config/crds/troubleshoot.sh_analyzers.yaml
+++ b/config/crds/troubleshoot.sh_analyzers.yaml
@@ -774,6 +774,58 @@ spec:
                       - namespace
                       - outcomes
                       type: object
+                    mssql:
+                      properties:
+                        annotations:
+                          additionalProperties:
+                            type: string
+                          type: object
+                        checkName:
+                          type: string
+                        collectorName:
+                          type: string
+                        exclude:
+                          type: BoolString
+                        fileName:
+                          type: string
+                        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:
+                      - collectorName
+                      - outcomes
+                      type: object
                     mysql:
                       properties:
                         annotations:
diff --git a/config/crds/troubleshoot.sh_collectors.yaml b/config/crds/troubleshoot.sh_collectors.yaml
index 0cafa2dc..56426279 100644
--- a/config/crds/troubleshoot.sh_collectors.yaml
+++ b/config/crds/troubleshoot.sh_collectors.yaml
@@ -359,6 +359,42 @@ spec:
                       required:
                       - namespace
                       type: object
+                    mssql:
+                      properties:
+                        collectorName:
+                          type: string
+                        exclude:
+                          type: BoolString
+                        parameters:
+                          items:
+                            type: string
+                          type: array
+                        tls:
+                          properties:
+                            cacert:
+                              type: string
+                            clientCert:
+                              type: string
+                            clientKey:
+                              type: string
+                            secret:
+                              properties:
+                                name:
+                                  type: string
+                                namespace:
+                                  type: string
+                              required:
+                              - name
+                              - namespace
+                              type: object
+                            skipVerify:
+                              type: boolean
+                          type: object
+                        uri:
+                          type: string
+                      required:
+                      - uri
+                      type: object
                     mysql:
                       properties:
                         collectorName:
@@ -2424,7 +2460,8 @@ spec:
                                           defined in spec.resourceClaims, that are
                                           used by this container. \n This is an alpha
                                           field and requires enabling the DynamicResourceAllocation
-                                          feature gate. \n This field is immutable."
+                                          feature gate. \n This field is immutable.
+                                          It can only be set for containers."
                                         items:
                                           description: ResourceClaim references one
                                             entry in PodSpec.ResourceClaims.
@@ -3895,7 +3932,8 @@ spec:
                                           defined in spec.resourceClaims, that are
                                           used by this container. \n This is an alpha
                                           field and requires enabling the DynamicResourceAllocation
-                                          feature gate. \n This field is immutable."
+                                          feature gate. \n This field is immutable.
+                                          It can only be set for containers."
                                         items:
                                           description: ResourceClaim references one
                                             entry in PodSpec.ResourceClaims.
@@ -5402,7 +5440,8 @@ spec:
                                           defined in spec.resourceClaims, that are
                                           used by this container. \n This is an alpha
                                           field and requires enabling the DynamicResourceAllocation
-                                          feature gate. \n This field is immutable."
+                                          feature gate. \n This field is immutable.
+                                          It can only be set for containers."
                                         items:
                                           description: ResourceClaim references one
                                             entry in PodSpec.ResourceClaims.
@@ -7332,7 +7371,8 @@ spec:
                                                       \n This is an alpha field and
                                                       requires enabling the DynamicResourceAllocation
                                                       feature gate. \n This field
-                                                      is immutable."
+                                                      is immutable. It can only be
+                                                      set for containers."
                                                     items:
                                                       description: ResourceClaim references
                                                         one entry in PodSpec.ResourceClaims.
diff --git a/config/crds/troubleshoot.sh_preflights.yaml b/config/crds/troubleshoot.sh_preflights.yaml
index c0efb475..9c4bc949 100644
--- a/config/crds/troubleshoot.sh_preflights.yaml
+++ b/config/crds/troubleshoot.sh_preflights.yaml
@@ -774,6 +774,58 @@ spec:
                       - namespace
                       - outcomes
                       type: object
+                    mssql:
+                      properties:
+                        annotations:
+                          additionalProperties:
+                            type: string
+                          type: object
+                        checkName:
+                          type: string
+                        collectorName:
+                          type: string
+                        exclude:
+                          type: BoolString
+                        fileName:
+                          type: string
+                        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:
+                      - collectorName
+                      - outcomes
+                      type: object
                     mysql:
                       properties:
                         annotations:
@@ -1751,6 +1803,42 @@ spec:
                       required:
                       - namespace
                       type: object
+                    mssql:
+                      properties:
+                        collectorName:
+                          type: string
+                        exclude:
+                          type: BoolString
+                        parameters:
+                          items:
+                            type: string
+                          type: array
+                        tls:
+                          properties:
+                            cacert:
+                              type: string
+                            clientCert:
+                              type: string
+                            clientKey:
+                              type: string
+                            secret:
+                              properties:
+                                name:
+                                  type: string
+                                namespace:
+                                  type: string
+                              required:
+                              - name
+                              - namespace
+                              type: object
+                            skipVerify:
+                              type: boolean
+                          type: object
+                        uri:
+                          type: string
+                      required:
+                      - uri
+                      type: object
                     mysql:
                       properties:
                         collectorName:
@@ -3816,7 +3904,8 @@ spec:
                                           defined in spec.resourceClaims, that are
                                           used by this container. \n This is an alpha
                                           field and requires enabling the DynamicResourceAllocation
-                                          feature gate. \n This field is immutable."
+                                          feature gate. \n This field is immutable.
+                                          It can only be set for containers."
                                         items:
                                           description: ResourceClaim references one
                                             entry in PodSpec.ResourceClaims.
@@ -5287,7 +5376,8 @@ spec:
                                           defined in spec.resourceClaims, that are
                                           used by this container. \n This is an alpha
                                           field and requires enabling the DynamicResourceAllocation
-                                          feature gate. \n This field is immutable."
+                                          feature gate. \n This field is immutable.
+                                          It can only be set for containers."
                                         items:
                                           description: ResourceClaim references one
                                             entry in PodSpec.ResourceClaims.
@@ -6794,7 +6884,8 @@ spec:
                                           defined in spec.resourceClaims, that are
                                           used by this container. \n This is an alpha
                                           field and requires enabling the DynamicResourceAllocation
-                                          feature gate. \n This field is immutable."
+                                          feature gate. \n This field is immutable.
+                                          It can only be set for containers."
                                         items:
                                           description: ResourceClaim references one
                                             entry in PodSpec.ResourceClaims.
@@ -8724,7 +8815,8 @@ spec:
                                                       \n This is an alpha field and
                                                       requires enabling the DynamicResourceAllocation
                                                       feature gate. \n This field
-                                                      is immutable."
+                                                      is immutable. It can only be
+                                                      set for containers."
                                                     items:
                                                       description: ResourceClaim references
                                                         one entry in PodSpec.ResourceClaims.
diff --git a/config/crds/troubleshoot.sh_supportbundles.yaml b/config/crds/troubleshoot.sh_supportbundles.yaml
index a2e1a9bd..bcff2cf3 100644
--- a/config/crds/troubleshoot.sh_supportbundles.yaml
+++ b/config/crds/troubleshoot.sh_supportbundles.yaml
@@ -805,6 +805,58 @@ spec:
                       - namespace
                       - outcomes
                       type: object
+                    mssql:
+                      properties:
+                        annotations:
+                          additionalProperties:
+                            type: string
+                          type: object
+                        checkName:
+                          type: string
+                        collectorName:
+                          type: string
+                        exclude:
+                          type: BoolString
+                        fileName:
+                          type: string
+                        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:
+                      - collectorName
+                      - outcomes
+                      type: object
                     mysql:
                       properties:
                         annotations:
@@ -1782,6 +1834,42 @@ spec:
                       required:
                       - namespace
                       type: object
+                    mssql:
+                      properties:
+                        collectorName:
+                          type: string
+                        exclude:
+                          type: BoolString
+                        parameters:
+                          items:
+                            type: string
+                          type: array
+                        tls:
+                          properties:
+                            cacert:
+                              type: string
+                            clientCert:
+                              type: string
+                            clientKey:
+                              type: string
+                            secret:
+                              properties:
+                                name:
+                                  type: string
+                                namespace:
+                                  type: string
+                              required:
+                              - name
+                              - namespace
+                              type: object
+                            skipVerify:
+                              type: boolean
+                          type: object
+                        uri:
+                          type: string
+                      required:
+                      - uri
+                      type: object
                     mysql:
                       properties:
                         collectorName:
@@ -3847,7 +3935,8 @@ spec:
                                           defined in spec.resourceClaims, that are
                                           used by this container. \n This is an alpha
                                           field and requires enabling the DynamicResourceAllocation
-                                          feature gate. \n This field is immutable."
+                                          feature gate. \n This field is immutable.
+                                          It can only be set for containers."
                                         items:
                                           description: ResourceClaim references one
                                             entry in PodSpec.ResourceClaims.
@@ -5318,7 +5407,8 @@ spec:
                                           defined in spec.resourceClaims, that are
                                           used by this container. \n This is an alpha
                                           field and requires enabling the DynamicResourceAllocation
-                                          feature gate. \n This field is immutable."
+                                          feature gate. \n This field is immutable.
+                                          It can only be set for containers."
                                         items:
                                           description: ResourceClaim references one
                                             entry in PodSpec.ResourceClaims.
@@ -6825,7 +6915,8 @@ spec:
                                           defined in spec.resourceClaims, that are
                                           used by this container. \n This is an alpha
                                           field and requires enabling the DynamicResourceAllocation
-                                          feature gate. \n This field is immutable."
+                                          feature gate. \n This field is immutable.
+                                          It can only be set for containers."
                                         items:
                                           description: ResourceClaim references one
                                             entry in PodSpec.ResourceClaims.
@@ -8755,7 +8846,8 @@ spec:
                                                       \n This is an alpha field and
                                                       requires enabling the DynamicResourceAllocation
                                                       feature gate. \n This field
-                                                      is immutable."
+                                                      is immutable. It can only be
+                                                      set for containers."
                                                     items:
                                                       description: ResourceClaim references
                                                         one entry in PodSpec.ResourceClaims.
diff --git a/deploy/.goreleaser.yaml b/deploy/.goreleaser.yaml
index 224e7b58..4ed3914d 100644
--- a/deploy/.goreleaser.yaml
+++ b/deploy/.goreleaser.yaml
@@ -13,7 +13,11 @@ builds:
       - windows
     goarch:
       - amd64
+      - arm
       - arm64
+    ignore:
+      - goos: windows
+        goarch: arm
     env:
       - CGO_ENABLED=0
     main: cmd/preflight/main.go
@@ -32,7 +36,11 @@ builds:
       - windows
     goarch:
       - amd64
+      - arm
       - arm64
+    ignore:
+      - goos: windows
+        goarch: arm
     env:
       - CGO_ENABLED=0
     main: cmd/troubleshoot/main.go
@@ -51,7 +59,11 @@ builds:
       - windows
     goarch:
       - amd64
+      - arm
       - arm64
+    ignore:
+      - goos: windows
+        goarch: arm
     env:
       - CGO_ENABLED=0
     main: cmd/collect/main.go
diff --git a/deploy/krew/preflight.yaml b/deploy/krew/preflight.yaml
index 8b463b3a..4f6da53f 100644
--- a/deploy/krew/preflight.yaml
+++ b/deploy/krew/preflight.yaml
@@ -16,6 +16,17 @@ spec:
     - from: LICENSE
       to: .
     bin: preflight
+  - selector:
+      matchLabels:
+        os: linux
+        arch: arm
+    {{addURIAndSha "https://github.com/replicatedhq/troubleshoot/releases/download/{{ .TagName }}/preflight_linux_arm_6.tar.gz" .TagName }}
+    files:
+    - from: preflight
+      to: .
+    - from: LICENSE
+      to: .
+    bin: preflight
   - selector:
       matchLabels:
         os: darwin
diff --git a/deploy/krew/support-bundle.yaml b/deploy/krew/support-bundle.yaml
index d1602dc9..0b4b1d1d 100644
--- a/deploy/krew/support-bundle.yaml
+++ b/deploy/krew/support-bundle.yaml
@@ -16,6 +16,17 @@ spec:
     - from: LICENSE
       to: .
     bin: support-bundle
+  - selector:
+      matchLabels:
+        os: linux
+        arch: arm
+    {{addURIAndSha "https://github.com/replicatedhq/troubleshoot/releases/download/{{ .TagName }}/support-bundle_linux_arm_6.tar.gz" .TagName }}
+    files:
+    - from: support-bundle
+      to: .
+    - from: LICENSE
+      to: .
+    bin: support-bundle
   - selector:
       matchLabels:
         os: darwin
diff --git a/examples/preflight/mssql.yaml b/examples/preflight/mssql.yaml
new file mode 100644
index 00000000..9aeb6fd8
--- /dev/null
+++ b/examples/preflight/mssql.yaml
@@ -0,0 +1,70 @@
+apiVersion: troubleshoot.sh/v1beta2
+kind: Preflight
+metadata:
+  name: sample
+spec:
+  collectors:
+    - mssql:
+        collectorName: mssql
+        uri: sqlserver://SA:nac3lle-ch@stise-bOMBay-wag@db.lab.shortrib.net:1433/master
+  analyzers:
+    - mssql:
+        checkName: Must be SQLServer 15.x or later
+        collectorName: mssql
+        outcomes:
+          - fail:
+              when: "connected == false"
+              message: Cannot connect to SQLServer
+          - fail:
+              when: "version < 15.x"
+              message: The SQLServer must be at least version 15
+          - pass:
+              message: The SQLServer connection checks out
+    - mssql:
+        checkName: Must be SQLServer 14 or earlier
+        collectorName: mssql
+        outcomes:
+          - fail:
+              when: "connected == false"
+              message: Cannot connect to SQLServer
+          - fail:
+              when: "version > 14.x"
+              message: The SQLServer must be at most version 14
+          - pass:
+              message: The SQLServer connection checks out
+    - mssql:
+        checkName: Must be SQLServer 15.0.2000.1565 
+        collectorName: mssql
+        outcomes:
+          - fail:
+              when: "connected == false"
+              message: Cannot connect to SQLServer
+          - fail:
+              when: "version == 15.0.2000.1565"
+              message: The SQLServer must be version 15.0.2000.1565 
+          - pass:
+              message: The SQLServer connection checks out
+    - mssql:
+        checkName: Must not be SQLServer 15.0.2000.1565 
+        collectorName: mssql
+        outcomes:
+          - fail:
+              when: "connected == false"
+              message: Cannot connect to SQLServer
+          - fail:
+              when: "version != 15.0.2000.1565"
+              message: The SQLServer must not be version 15.0.2000.1565 
+          - pass:
+              message: The SQLServer connection checks out
+    - mssql:
+        checkName: I don't want to connect to your SQL Server
+        collectorName: mssql
+        outcomes:
+          - fail:
+              when: "connected == true"
+              message: Can connect to your SQL Server
+          - fail:
+              when: "version != 15.0.2000.1565"
+              message: The SQLServer must be version 15.0.2000.1565 
+          - pass:
+              message: The SQLServer connection checks out
diff --git a/go.mod b/go.mod
index 5c5b4c42..458ba847 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@ require (
 	github.com/blang/semver/v4 v4.0.0
 	github.com/containers/image/v5 v5.24.2
 	github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2
-	github.com/fatih/color v1.14.1
+	github.com/fatih/color v1.15.0
 	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
@@ -24,6 +24,7 @@ require (
 	github.com/manifoldco/promptui v0.9.0
 	github.com/mattn/go-isatty v0.0.17
 	github.com/mholt/archiver/v3 v3.5.1
+	github.com/microsoft/go-mssqldb v0.18.0
 	github.com/opencontainers/image-spec v1.1.0-rc2
 	github.com/pkg/errors v0.9.1
 	github.com/replicatedhq/termui/v3 v3.1.1-0.20200811145416-f40076d26851
@@ -39,12 +40,12 @@ require (
 	go.opentelemetry.io/otel/sdk v1.14.0
 	golang.org/x/sync v0.1.0
 	gopkg.in/yaml.v2 v2.4.0
-	k8s.io/api v0.26.2
-	k8s.io/apiextensions-apiserver v0.26.2
-	k8s.io/apimachinery v0.26.2
-	k8s.io/apiserver v0.26.2
-	k8s.io/cli-runtime v0.26.1
-	k8s.io/client-go v0.26.2
+	k8s.io/api v0.26.3
+	k8s.io/apiextensions-apiserver v0.26.3
+	k8s.io/apimachinery v0.26.3
+	k8s.io/apiserver v0.26.3
+	k8s.io/cli-runtime v0.26.3
+	k8s.io/client-go v0.26.3
 	k8s.io/klog/v2 v2.90.0
 	oras.land/oras-go v1.2.2
 	sigs.k8s.io/controller-runtime v0.14.5
@@ -57,6 +58,8 @@ require (
 	github.com/emicklei/go-restful/v3 v3.9.0 // indirect
 	github.com/evanphx/json-patch/v5 v5.6.0 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
+	github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect
+	github.com/golang-sql/sqlexp v0.1.0 // indirect
 	github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect
 	github.com/jackc/pgpassfile v1.0.0 // indirect
 	github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
@@ -66,8 +69,8 @@ require (
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/sylabs/sif/v2 v2.9.0 // indirect
 	go.opentelemetry.io/otel/trace v1.14.0 // indirect
-	golang.org/x/mod v0.7.0 // indirect
-	golang.org/x/tools v0.4.0 // indirect
+	golang.org/x/mod v0.8.0 // indirect
+	golang.org/x/tools v0.6.0 // indirect
 )
 
 require (
@@ -126,7 +129,7 @@ require (
 	github.com/hashicorp/errwrap v1.1.0 // indirect
 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
 	github.com/hashicorp/go-safetemp v1.0.0 // indirect
-	github.com/hashicorp/go-version v1.6.0 // indirect
+	github.com/hashicorp/go-version v1.6.0
 	github.com/hashicorp/hcl v1.0.0 // indirect
 	github.com/imdario/mergo v0.3.13 // indirect
 	github.com/inconshreveable/mousetrap v1.0.1 // indirect
@@ -189,9 +192,9 @@ require (
 	golang.org/x/crypto v0.6.0 // indirect
 	golang.org/x/net v0.7.0 // indirect
 	golang.org/x/oauth2 v0.4.0 // indirect
-	golang.org/x/sys v0.5.0 // indirect
+	golang.org/x/sys v0.6.0 // indirect
 	golang.org/x/term v0.5.0 // indirect
-	golang.org/x/text v0.7.0
+	golang.org/x/text v0.8.0
 	golang.org/x/time v0.3.0 // indirect
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
 	google.golang.org/api v0.107.0 // indirect
diff --git a/go.sum b/go.sum
index 640aca97..9b723f43 100644
--- a/go.sum
+++ b/go.sum
@@ -191,6 +191,10 @@ cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoIS
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774 h1:SCbEWT58NSt7d2mcFdvxC9uyrdcTfvBbPLThhkDmXzg=
 github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.2/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0/go.mod h1:bhXu1AjYL+wutSL/kpSq6s7733q2Rb0yuot9Zgfqa/0=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
 github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
@@ -204,6 +208,7 @@ github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935
 github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
 github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
+github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
 github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
@@ -457,6 +462,8 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
 github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc=
 github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI=
 github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
+github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
+github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
 github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 github.com/docker/cli v20.10.21+incompatible h1:qVkgyYUnOLQ98LtXBrwd/duVqPT2X4SHndOuGsfwyhU=
 github.com/docker/cli v20.10.21+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
@@ -515,8 +522,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.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
-github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
+github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
+github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
 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=
@@ -598,6 +605,13 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
+github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
+github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
+github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
+github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
+github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
+github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -724,6 +738,8 @@ github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
 github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@@ -749,6 +765,7 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
 github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
 github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
 github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
 github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -776,6 +793,12 @@ github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/
 github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
 github.com/jackc/pgx/v5 v5.3.0 h1:/NQi8KHMpKWHInxXesC8yD4DhkXPrVhmnwYkjp9AmBA=
 github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
+github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
+github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
+github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
+github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
+github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
+github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
@@ -829,6 +852,7 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
 github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
 github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
 github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
@@ -871,6 +895,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfr
 github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
 github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
 github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
+github.com/microsoft/go-mssqldb v0.18.0 h1:6Pf95ZIt8mjg3Bm374SlH7RFiez1nGy4xKKOiCw/zFE=
+github.com/microsoft/go-mssqldb v0.18.0/go.mod h1:ukJCBnnzLzpVF0qYRT+eg1e+eSwjeQ7IvenUv8QPook=
 github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
 github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
 github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
@@ -907,8 +933,10 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
 github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
 github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
+github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
 github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
 github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
 github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
@@ -992,6 +1020,8 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR
 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
 github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
 github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
+github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -1236,10 +1266,13 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1278,8 +1311,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
-golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1318,6 +1351,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
 golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@@ -1330,6 +1364,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
@@ -1477,6 +1512,7 @@ golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1491,6 +1527,7 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1508,8 +1545,9 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1525,8 +1563,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1600,8 +1638,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4=
-golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
+golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1859,6 +1897,8 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
+gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
+gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
@@ -1877,6 +1917,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -1895,27 +1936,27 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
 k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo=
 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.2 h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ=
-k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU=
-k8s.io/apiextensions-apiserver v0.26.2 h1:/yTG2B9jGY2Q70iGskMf41qTLhL9XeNN2KhI0uDgwko=
-k8s.io/apiextensions-apiserver v0.26.2/go.mod h1:Y7UPgch8nph8mGCuVk0SK83LnS8Esf3n6fUBgew8SH8=
+k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU=
+k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE=
+k8s.io/apiextensions-apiserver v0.26.3 h1:5PGMm3oEzdB1W/FTMgGIDmm100vn7IaUP5er36dB+YE=
+k8s.io/apiextensions-apiserver v0.26.3/go.mod h1:jdA5MdjNWGP+njw1EKMZc64xAT5fIhN6VJrElV3sfpQ=
 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=
-k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ=
-k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I=
+k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k=
+k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I=
 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.2 h1:Pk8lmX4G14hYqJd1poHGC08G03nIHVqdJMR0SD3IH3o=
-k8s.io/apiserver v0.26.2/go.mod h1:GHcozwXgXsPuOJ28EnQ/jXEM9QeG6HT22YxSNmpYNh8=
-k8s.io/cli-runtime v0.26.1 h1:f9+bRQ1V3elQsx37KmZy5fRAh56mVLbE9A7EMdlqVdI=
-k8s.io/cli-runtime v0.26.1/go.mod h1:+e5Ym/ARySKscUhZ8K3hZ+ZBo/wYPIcg+7b5sFYi6Gg=
+k8s.io/apiserver v0.26.3 h1:blBpv+yOiozkPH2aqClhJmJY+rp53Tgfac4SKPDJnU4=
+k8s.io/apiserver v0.26.3/go.mod h1:CJe/VoQNcXdhm67EvaVjYXxR3QyfwpceKPuPaeLibTA=
+k8s.io/cli-runtime v0.26.3 h1:3ULe0oI28xmgeLMVXIstB+ZL5CTGvWSMVMLeHxitIuc=
+k8s.io/cli-runtime v0.26.3/go.mod h1:5YEhXLV4kLt/OSy9yQwtSSNZU2Z7aTEYta1A+Jg4VC4=
 k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y=
 k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k=
 k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0=
-k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI=
-k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU=
+k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s=
+k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ=
 k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0=
 k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk=
 k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI=
diff --git a/pkg/analyze/analyzer.go b/pkg/analyze/analyzer.go
index 9fd72878..d6693988 100644
--- a/pkg/analyze/analyzer.go
+++ b/pkg/analyze/analyzer.go
@@ -2,6 +2,7 @@ package analyzer
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"reflect"
 	"strconv"
@@ -235,3 +236,26 @@ func getAnalyzer(analyzer *troubleshootv1beta2.Analyze) Analyzer {
 		return nil
 	}
 }
+
+// deduplicates a list of troubleshootv1beta2.Analyze objects
+// marshals object to json and then uses its string value to check for uniqueness
+// there is no sorting of the keys in the analyze object's spec so if the spec isn't an exact match line for line as written, no dedup will occur
+func DedupAnalyzers(allAnalyzers []*troubleshootv1beta2.Analyze) []*troubleshootv1beta2.Analyze {
+	uniqueAnalyzers := make(map[string]bool)
+	finalAnalyzers := []*troubleshootv1beta2.Analyze{}
+
+	for _, analyzer := range allAnalyzers {
+		data, err := json.Marshal(analyzer)
+		if err != nil {
+			// return analyzer as is if for whatever reason it can't be marshalled into json
+			finalAnalyzers = append(finalAnalyzers, analyzer)
+		} else {
+			stringData := string(data)
+			if _, value := uniqueAnalyzers[stringData]; !value {
+				uniqueAnalyzers[stringData] = true
+				finalAnalyzers = append(finalAnalyzers, analyzer)
+			}
+		}
+	}
+	return finalAnalyzers
+}
diff --git a/pkg/analyze/host_block_devices.go b/pkg/analyze/host_block_devices.go
index 06d84595..3e73a8c0 100644
--- a/pkg/analyze/host_block_devices.go
+++ b/pkg/analyze/host_block_devices.go
@@ -116,7 +116,7 @@ func (a *AnalyzeHostBlockDevices) Analyze(getCollectedFileContents func(string)
 func compareHostBlockDevicesConditionalToActual(conditional string, minimumAcceptableSize uint64, includeUnmountedPartitions bool, devices []collect.BlockDeviceInfo) (res bool, err error) {
 	parts := strings.Split(conditional, " ")
 	if len(parts) != 3 {
-		return false, fmt.Errorf("Expected exactly 3 parts, got %d", len(parts))
+		return false, fmt.Errorf("expected exactly 3 parts, got %d", len(parts))
 	}
 
 	rx, err := regexp.Compile(parts[0])
@@ -143,7 +143,7 @@ func compareHostBlockDevicesConditionalToActual(conditional string, minimumAccep
 		return count == desiredInt, nil
 	}
 
-	return false, fmt.Errorf("Unexpected operator %q", parts[1])
+	return false, fmt.Errorf("unexpected operator %q", parts[1])
 }
 
 func countEligibleBlockDevices(rx *regexp.Regexp, minimumAcceptableSize uint64, includeUnmountedPartitions bool, devices []collect.BlockDeviceInfo) int {
diff --git a/pkg/analyze/mssql.go b/pkg/analyze/mssql.go
new file mode 100644
index 00000000..71c55d94
--- /dev/null
+++ b/pkg/analyze/mssql.go
@@ -0,0 +1,172 @@
+package analyzer
+
+import (
+	"encoding/json"
+	"fmt"
+	"path"
+	"strconv"
+	"strings"
+
+	"github.com/hashicorp/go-version"
+	"github.com/pkg/errors"
+	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"github.com/replicatedhq/troubleshoot/pkg/collect"
+)
+
+func compareMssqlConditionalToActual(conditional string, result *collect.DatabaseConnection) (bool, error) {
+	parts := strings.Split(strings.TrimSpace(conditional), " ")
+
+	if len(parts) != 3 {
+		return false, errors.New("unable to parse conditional")
+	}
+
+	switch parts[0] {
+	case "connected":
+		expected, err := strconv.ParseBool(parts[2])
+		if err != nil {
+			return false, errors.Wrap(err, "failed to parse bool")
+		}
+
+		switch parts[1] {
+		case "=", "==", "===":
+			return expected == result.IsConnected, nil
+		case "!=", "!==":
+			return expected != result.IsConnected, nil
+
+		}
+
+		return false, errors.New("unable to parse postgres connected analyzer")
+
+	case "version":
+		expected, err := version.NewVersion(strings.Replace(parts[2], "x", "0", -1))
+		if err != nil {
+			return false, errors.Wrap(err, "failed to parse expected version")
+		}
+
+		operation := parts[1]
+		switch operation {
+		case "=", "==", "===":
+			operation = "="
+		case "!=", "!==":
+			operation = "!="
+		}
+
+		actual, err := version.NewVersion(strings.Replace(result.Version, "x", "0", -1))
+		if err != nil {
+			return false, errors.Wrap(err, "failed to parse mssql db actual version")
+		}
+
+		constraints, err := version.NewConstraint(fmt.Sprintf("%s %s", operation, expected))
+		if err != nil {
+			return false, errors.Wrap(err, "failed to create constraint")
+		}
+		return constraints.Check(actual), nil
+	}
+
+	return false, nil
+}
+
+func analyzeMssql(analyzer *troubleshootv1beta2.DatabaseAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
+	collectorName := analyzer.CollectorName
+	if collectorName == "" {
+		collectorName = "mssql"
+	}
+
+	fullPath := path.Join("mssql", fmt.Sprintf("%s.json", collectorName))
+
+	collected, err := getCollectedFileContents(fullPath)
+	if err != nil {
+		return nil, errors.Wrapf(err, "failed to read collected file name: %s", fullPath)
+	}
+
+	databaseConnection := collect.DatabaseConnection{}
+	if err := json.Unmarshal(collected, &databaseConnection); err != nil {
+		return nil, errors.Wrap(err, "failed to unmarshal databased connection result")
+	}
+
+	title := analyzer.CheckName
+	if title == "" {
+		title = collectorName
+	}
+
+	result := &AnalyzeResult{
+		Title:   title,
+		IconKey: "kubernetes_sqlserver_analyze",
+		IconURI: "https://troubleshoot.sh/images/analyzer-icons/sqlserver-analyze.svg",
+	}
+
+	for _, outcome := range analyzer.Outcomes {
+		if outcome.Fail != nil {
+			if outcome.Fail.When == "" {
+				result.IsFail = true
+				result.Message = outcome.Fail.Message
+				result.URI = outcome.Fail.URI
+
+				return result, nil
+			}
+
+			isMatch, err := compareMssqlConditionalToActual(outcome.Fail.When, &databaseConnection)
+			if err != nil {
+				return result, errors.Wrap(err, "failed to compare MS SQL Server database conditional")
+			}
+
+			if isMatch {
+
+				if databaseConnection.Error != "" {
+					result.Message = outcome.Fail.Message + " " + databaseConnection.Error
+				} else {
+					result.Message = outcome.Fail.Message
+				}
+
+				result.IsFail = true
+				result.URI = outcome.Fail.URI
+
+				return 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 result, nil
+			}
+
+			isMatch, err := compareMssqlConditionalToActual(outcome.Warn.When, &databaseConnection)
+			if err != nil {
+				return result, errors.Wrap(err, "failed to compare MS SQL Server database conditional")
+			}
+
+			if isMatch {
+				result.IsWarn = true
+				result.Message = outcome.Warn.Message
+				result.URI = outcome.Warn.URI
+
+				return 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 result, nil
+			}
+
+			isMatch, err := compareMssqlConditionalToActual(outcome.Pass.When, &databaseConnection)
+			if err != nil {
+				return result, errors.Wrap(err, "failed to compare MS SQL Server database conditional")
+			}
+
+			if isMatch {
+				result.IsPass = true
+				result.Message = outcome.Pass.Message
+				result.URI = outcome.Pass.URI
+
+				return result, nil
+			}
+		}
+	}
+
+	return result, nil
+}
diff --git a/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go b/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go
index c14e6f08..b7e9c79b 100644
--- a/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go
+++ b/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go
@@ -233,6 +233,7 @@ type Analyze struct {
 	YamlCompare              *YamlCompare              `json:"yamlCompare,omitempty" yaml:"yamlCompare,omitempty"`
 	JsonCompare              *JsonCompare              `json:"jsonCompare,omitempty" yaml:"jsonCompare,omitempty"`
 	Postgres                 *DatabaseAnalyze          `json:"postgres,omitempty" yaml:"postgres,omitempty"`
+	Mssql                    *DatabaseAnalyze          `json:"mssql,omitempty" yaml:"mssql,omitempty"`
 	Mysql                    *DatabaseAnalyze          `json:"mysql,omitempty" yaml:"mysql,omitempty"`
 	Redis                    *DatabaseAnalyze          `json:"redis,omitempty" yaml:"redis,omitempty"`
 	CephStatus               *CephStatusAnalyze        `json:"cephStatus,omitempty" yaml:"cephStatus,omitempty"`
diff --git a/pkg/apis/troubleshoot/v1beta2/collector_shared.go b/pkg/apis/troubleshoot/v1beta2/collector_shared.go
index 70e63bd8..77406027 100644
--- a/pkg/apis/troubleshoot/v1beta2/collector_shared.go
+++ b/pkg/apis/troubleshoot/v1beta2/collector_shared.go
@@ -230,6 +230,7 @@ type Collect struct {
 	CopyFromHost     *CopyFromHost     `json:"copyFromHost,omitempty" yaml:"copyFromHost,omitempty"`
 	HTTP             *HTTP             `json:"http,omitempty" yaml:"http,omitempty"`
 	Postgres         *Database         `json:"postgres,omitempty" yaml:"postgres,omitempty"`
+	Mssql            *Database         `json:"mssql,omitempty" yaml:"mssql,omitempty"`
 	Mysql            *Database         `json:"mysql,omitempty" yaml:"mysql,omitempty"`
 	Redis            *Database         `json:"redis,omitempty" yaml:"redis,omitempty"`
 	Collectd         *Collectd         `json:"collectd,omitempty" yaml:"collectd,omitempty"`
@@ -505,6 +506,10 @@ func (c *Collect) GetName() string {
 		collector = "postgres"
 		name = c.Postgres.CollectorName
 	}
+	if c.Mssql != nil {
+		collector = "mssql"
+		name = c.Mssql.CollectorName
+	}
 	if c.Mysql != nil {
 		collector = "mysql"
 		name = c.Mysql.CollectorName
diff --git a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go
index fdd2b604..abf8fbe3 100644
--- a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go
+++ b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go
@@ -149,6 +149,11 @@ func (in *Analyze) DeepCopyInto(out *Analyze) {
 		*out = new(DatabaseAnalyze)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Mssql != nil {
+		in, out := &in.Mssql, &out.Mssql
+		*out = new(DatabaseAnalyze)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.Mysql != nil {
 		in, out := &in.Mysql, &out.Mysql
 		*out = new(DatabaseAnalyze)
@@ -745,6 +750,11 @@ func (in *Collect) DeepCopyInto(out *Collect) {
 		*out = new(Database)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Mssql != nil {
+		in, out := &in.Mssql, &out.Mssql
+		*out = new(Database)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.Mysql != nil {
 		in, out := &in.Mysql, &out.Mysql
 		*out = new(Database)
diff --git a/pkg/collect/collector.go b/pkg/collect/collector.go
index fa57b09f..72ffde1c 100644
--- a/pkg/collect/collector.go
+++ b/pkg/collect/collector.go
@@ -85,6 +85,8 @@ func GetCollector(collector *troubleshootv1beta2.Collect, bundlePath string, nam
 		return &CollectHTTP{collector.HTTP, bundlePath, namespace, clientConfig, client, RBACErrors}, true
 	case collector.Postgres != nil:
 		return &CollectPostgres{collector.Postgres, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
+	case collector.Mssql != nil:
+		return &CollectMssql{collector.Mssql, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
 	case collector.Mysql != nil:
 		return &CollectMysql{collector.Mysql, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
 	case collector.Redis != nil:
@@ -150,6 +152,9 @@ func getCollectorName(c interface{}) string {
 	case *CollectPostgres:
 		collector = "postgres"
 		name = v.Collector.CollectorName
+	case *CollectMssql:
+		collector = "mssql"
+		name = v.Collector.CollectorName
 	case *CollectMysql:
 		collector = "mysql"
 		name = v.Collector.CollectorName
diff --git a/pkg/collect/host_block_device.go b/pkg/collect/host_block_device.go
index b6e553c1..3564bf9e 100644
--- a/pkg/collect/host_block_device.go
+++ b/pkg/collect/host_block_device.go
@@ -13,22 +13,23 @@ import (
 )
 
 type BlockDeviceInfo struct {
-	Name             string `json:"name"`
-	KernelName       string `json:"kernel_name"`
-	ParentKernelName string `json:"parent_kernel_name"`
-	Type             string `json:"type"`
-	Major            int    `json:"major"`
-	Minor            int    `json:"minor"`
-	Size             uint64 `json:"size"`
-	FilesystemType   string `json:"filesystem_type"`
-	Mountpoint       string `json:"mountpoint"`
-	Serial           string `json:"serial"`
-	ReadOnly         bool   `json:"read_only"`
-	Removable        bool   `json:"removable"`
+	Name               string `json:"name"`
+	KernelName         string `json:"kernel_name"`
+	ParentKernelName   string `json:"parent_kernel_name"`
+	Type               string `json:"type"`
+	Major              int    `json:"major"`
+	Minor              int    `json:"minor"`
+	Size               uint64 `json:"size"`
+	FilesystemType     string `json:"filesystem_type"`
+	Mountpoint         string `json:"mountpoint"`
+	Serial             string `json:"serial"`
+	ReadOnly           bool   `json:"read_only"`
+	Removable          bool   `json:"removable"`
+	PartitionTableType string `json:"partition_table_type"`
 }
 
-const lsblkColumns = "NAME,KNAME,PKNAME,TYPE,MAJ:MIN,SIZE,FSTYPE,MOUNTPOINT,SERIAL,RO,RM"
-const lsblkFormat = `NAME=%q KNAME=%q PKNAME=%q TYPE=%q MAJ:MIN="%d:%d" SIZE="%d" FSTYPE=%q MOUNTPOINT=%q SERIAL=%q RO="%d" RM="%d0"`
+const lsblkColumns = "NAME,KNAME,PKNAME,TYPE,MAJ:MIN,SIZE,FSTYPE,MOUNTPOINT,SERIAL,RO,RM,PTTYPE"
+const lsblkFormat = `NAME=%q KNAME=%q PKNAME=%q TYPE=%q MAJ:MIN="%d:%d" SIZE="%d" FSTYPE=%q MOUNTPOINT=%q SERIAL=%q RO="%d" RM="%d0" PTTYPE=%q`
 const HostBlockDevicesPath = `host-collectors/system/block_devices.json`
 
 type CollectHostBlockDevices struct {
@@ -45,6 +46,11 @@ func (c *CollectHostBlockDevices) IsExcluded() (bool, error) {
 }
 
 func (c *CollectHostBlockDevices) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
+	// TODO: Consider using lsblk --json --output <columns> instead.
+	// This simplifies the parsing logic and also represents a parent/child relationship
+	// between devices when there are partitions.
+	// NOTE: Remember to validate the output of lsblk json
+	// NOTE: Check what version of lsblk --json was introduced.
 	cmd := exec.Command("lsblk", "--noheadings", "--bytes", "--pairs", "-o", lsblkColumns)
 	stdout, err := cmd.Output()
 	if err != nil {
@@ -95,6 +101,7 @@ func parseLsblkOutput(output []byte) ([]BlockDeviceInfo, error) {
 			&bdi.Serial,
 			&ro,
 			&rm,
+			&bdi.PartitionTableType,
 		)
 		bdi.ReadOnly = ro == 1
 		bdi.Removable = rm == 1
diff --git a/pkg/collect/longhorn.go b/pkg/collect/longhorn.go
index 45c525cb..0a00c5c3 100644
--- a/pkg/collect/longhorn.go
+++ b/pkg/collect/longhorn.go
@@ -276,19 +276,24 @@ func (c *CollectLonghorn) Collect(progressChan chan<- interface{}) (CollectorRes
 			}
 
 			wg.Add(1)
-			go func(replica longhornv1beta1types.Replica) {
+			go func(repl *longhornv1beta1types.Replica, pName string, cfg *rest.Config, vol *longhornv1beta1types.Volume) {
 				defer wg.Done()
-				checksums, err := GetLonghornReplicaChecksum(c.ClientConfig, replica, podName)
+				checksums, err := GetLonghornReplicaChecksum(cfg, *repl, pName)
 				if err != nil {
-					klog.Errorf("Failed to get replica %s checksum: %v", replica.Name, err)
+					klog.Errorf("Failed to get replica %q checksum: %v", repl.Name, err)
 					return
 				}
 				volsDir := GetLonghornVolumesDirectory(ns)
-				key := filepath.Join(volsDir, volume.Name, "replicachecksums", replica.Name+".txt")
+				key := filepath.Join(volsDir, vol.Name, "replicachecksums", repl.Name+".txt")
+
+				// Locking is required because the output object is shared between goroutines
 				mtx.Lock()
 				output.SaveResult(c.BundlePath, key, bytes.NewBuffer([]byte(checksums)))
 				mtx.Unlock()
-			}(replica)
+
+				// make separate new copies of CR objects so as not to share internal memory
+				// of objects between goroutines
+			}(replica.DeepCopy(), podName, rest.CopyConfig(c.ClientConfig), volume.DeepCopy())
 		}
 	}
 
diff --git a/pkg/collect/mssql.go b/pkg/collect/mssql.go
new file mode 100644
index 00000000..47b71f07
--- /dev/null
+++ b/pkg/collect/mssql.go
@@ -0,0 +1,86 @@
+package collect
+
+import (
+	"bytes"
+	"context"
+	"database/sql"
+	"encoding/json"
+	"fmt"
+	"regexp"
+
+	_ "github.com/microsoft/go-mssqldb"
+	"github.com/pkg/errors"
+	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/rest"
+)
+
+type CollectMssql struct {
+	Collector    *troubleshootv1beta2.Database
+	BundlePath   string
+	Namespace    string
+	ClientConfig *rest.Config
+	Client       kubernetes.Interface
+	Context      context.Context
+	RBACErrors
+}
+
+func (c *CollectMssql) Title() string {
+	return getCollectorName(c)
+}
+
+func (c *CollectMssql) IsExcluded() (bool, error) {
+	return isExcluded(c.Collector.Exclude)
+}
+
+func (c *CollectMssql) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
+	databaseConnection := DatabaseConnection{}
+
+	db, err := sql.Open("mssql", c.Collector.URI)
+	if err != nil {
+		databaseConnection.Error = err.Error()
+	} else {
+		query := `select @@VERSION as version`
+		row := db.QueryRow(query)
+		version := ""
+		if err := row.Scan(&version); err != nil {
+			databaseConnection.Error = err.Error()
+		} else {
+			databaseConnection.IsConnected = true
+
+			mssqlVersion, err := parseMsSqlVersion(version)
+			if err != nil {
+				databaseConnection.Version = "Unknown"
+				databaseConnection.Error = err.Error()
+			} else {
+				databaseConnection.Version = mssqlVersion
+			}
+		}
+	}
+
+	b, err := json.Marshal(databaseConnection)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to marshal database connection")
+	}
+
+	collectorName := c.Collector.CollectorName
+	if collectorName == "" {
+		collectorName = "mssql"
+	}
+
+	output := NewResult()
+	output.SaveResult(c.BundlePath, fmt.Sprintf("mssql/%s.json", collectorName), bytes.NewBuffer(b))
+
+	return output, nil
+}
+
+func parseMsSqlVersion(mssqlVersion string) (string, error) {
+	re := regexp.MustCompile(".*SQL.*-\\s+([0-9.]+)")
+	matches := re.FindStringSubmatch(mssqlVersion)
+	if len(matches) < 2 {
+		return "", errors.Errorf("mssql version did not match regex: %q", mssqlVersion)
+	}
+
+	return matches[1], nil
+
+}
diff --git a/pkg/k8sutil/namespace.go b/pkg/k8sutil/namespace.go
new file mode 100644
index 00000000..0d6ec0b3
--- /dev/null
+++ b/pkg/k8sutil/namespace.go
@@ -0,0 +1,34 @@
+package k8sutil
+
+import (
+	"context"
+
+	authorizationv1 "k8s.io/api/authorization/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes"
+)
+
+func IsNamespacedScopeRBAC(client kubernetes.Interface) (bool, error) {
+	ctx := context.Background()
+
+	sar := &authorizationv1.SelfSubjectAccessReview{
+		Spec: authorizationv1.SelfSubjectAccessReviewSpec{
+			ResourceAttributes: &authorizationv1.ResourceAttributes{
+				Namespace: "",
+				Verb:      "list",
+				Resource:  "secrets,configmaps",
+			},
+		},
+	}
+
+	resp, err := client.AuthorizationV1().SelfSubjectAccessReviews().Create(ctx, sar, metav1.CreateOptions{})
+	if err != nil {
+		return false, err
+	}
+
+	if resp.Status.Allowed {
+		return true, nil
+	} else {
+		return false, nil
+	}
+}
diff --git a/pkg/supportbundle/supportbundle.go b/pkg/supportbundle/supportbundle.go
index 9783b923..019fa30f 100644
--- a/pkg/supportbundle/supportbundle.go
+++ b/pkg/supportbundle/supportbundle.go
@@ -255,6 +255,7 @@ func AnalyzeSupportBundle(ctx context.Context, spec *troubleshootv1beta2.Support
 	if len(spec.Analyzers) == 0 && len(spec.HostAnalyzers) == 0 {
 		return nil, nil
 	}
+	spec.Analyzers = analyzer.DedupAnalyzers(spec.Analyzers)
 	analyzeResults, err := analyzer.AnalyzeLocal(ctx, tmpDir, spec.Analyzers, spec.HostAnalyzers)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to analyze support bundle")
diff --git a/schemas/analyzer-troubleshoot-v1beta2.json b/schemas/analyzer-troubleshoot-v1beta2.json
index bde7047e..9cfc5368 100644
--- a/schemas/analyzer-troubleshoot-v1beta2.json
+++ b/schemas/analyzer-troubleshoot-v1beta2.json
@@ -1154,6 +1154,86 @@
                   }
                 }
               },
+              "mssql": {
+                "type": "object",
+                "required": [
+                  "collectorName",
+                  "outcomes"
+                ],
+                "properties": {
+                  "annotations": {
+                    "type": "object",
+                    "additionalProperties": {
+                      "type": "string"
+                    }
+                  },
+                  "checkName": {
+                    "type": "string"
+                  },
+                  "collectorName": {
+                    "type": "string"
+                  },
+                  "exclude": {
+                    "oneOf": [{"type": "string"},{"type": "boolean"}]
+                  },
+                  "fileName": {
+                    "type": "string"
+                  },
+                  "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"}]
+                  }
+                }
+              },
               "mysql": {
                 "type": "object",
                 "required": [
diff --git a/schemas/collector-troubleshoot-v1beta2.json b/schemas/collector-troubleshoot-v1beta2.json
index 7f9f524b..e0c8b7fb 100644
--- a/schemas/collector-troubleshoot-v1beta2.json
+++ b/schemas/collector-troubleshoot-v1beta2.json
@@ -507,6 +507,61 @@
                   }
                 }
               },
+              "mssql": {
+                "type": "object",
+                "required": [
+                  "uri"
+                ],
+                "properties": {
+                  "collectorName": {
+                    "type": "string"
+                  },
+                  "exclude": {
+                    "oneOf": [{"type": "string"},{"type": "boolean"}]
+                  },
+                  "parameters": {
+                    "type": "array",
+                    "items": {
+                      "type": "string"
+                    }
+                  },
+                  "tls": {
+                    "type": "object",
+                    "properties": {
+                      "cacert": {
+                        "type": "string"
+                      },
+                      "clientCert": {
+                        "type": "string"
+                      },
+                      "clientKey": {
+                        "type": "string"
+                      },
+                      "secret": {
+                        "type": "object",
+                        "required": [
+                          "name",
+                          "namespace"
+                        ],
+                        "properties": {
+                          "name": {
+                            "type": "string"
+                          },
+                          "namespace": {
+                            "type": "string"
+                          }
+                        }
+                      },
+                      "skipVerify": {
+                        "type": "boolean"
+                      }
+                    }
+                  },
+                  "uri": {
+                    "type": "string"
+                  }
+                }
+              },
               "mysql": {
                 "type": "object",
                 "required": [
@@ -2227,7 +2282,7 @@
                               "type": "object",
                               "properties": {
                                 "claims": {
-                                  "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable.",
+                                  "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers.",
                                   "type": "array",
                                   "items": {
                                     "description": "ResourceClaim references one entry in PodSpec.ResourceClaims.",
@@ -3423,7 +3478,7 @@
                               "type": "object",
                               "properties": {
                                 "claims": {
-                                  "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable.",
+                                  "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers.",
                                   "type": "array",
                                   "items": {
                                     "description": "ResourceClaim references one entry in PodSpec.ResourceClaims.",
@@ -4634,7 +4689,7 @@
                               "type": "object",
                               "properties": {
                                 "claims": {
-                                  "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable.",
+                                  "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers.",
                                   "type": "array",
                                   "items": {
                                     "description": "ResourceClaim references one entry in PodSpec.ResourceClaims.",
@@ -5891,7 +5946,7 @@
                                           "type": "object",
                                           "properties": {
                                             "claims": {
-                                              "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable.",
+                                              "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers.",
                                               "type": "array",
                                               "items": {
                                                 "description": "ResourceClaim references one entry in PodSpec.ResourceClaims.",
diff --git a/schemas/preflight-troubleshoot-v1beta2.json b/schemas/preflight-troubleshoot-v1beta2.json
index 855699c5..8a9f3ffa 100644
--- a/schemas/preflight-troubleshoot-v1beta2.json
+++ b/schemas/preflight-troubleshoot-v1beta2.json
@@ -1154,6 +1154,86 @@
                   }
                 }
               },
+              "mssql": {
+                "type": "object",
+                "required": [
+                  "collectorName",
+                  "outcomes"
+                ],
+                "properties": {
+                  "annotations": {
+                    "type": "object",
+                    "additionalProperties": {
+                      "type": "string"
+                    }
+                  },
+                  "checkName": {
+                    "type": "string"
+                  },
+                  "collectorName": {
+                    "type": "string"
+                  },
+                  "exclude": {
+                    "oneOf": [{"type": "string"},{"type": "boolean"}]
+                  },
+                  "fileName": {
+                    "type": "string"
+                  },
+                  "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"}]
+                  }
+                }
+              },
               "mysql": {
                 "type": "object",
                 "required": [
@@ -2651,6 +2731,61 @@
                   }
                 }
               },
+              "mssql": {
+                "type": "object",
+                "required": [
+                  "uri"
+                ],
+                "properties": {
+                  "collectorName": {
+                    "type": "string"
+                  },
+                  "exclude": {
+                    "oneOf": [{"type": "string"},{"type": "boolean"}]
+                  },
+                  "parameters": {
+                    "type": "array",
+                    "items": {
+                      "type": "string"
+                    }
+                  },
+                  "tls": {
+                    "type": "object",
+                    "properties": {
+                      "cacert": {
+                        "type": "string"
+                      },
+                      "clientCert": {
+                        "type": "string"
+                      },
+                      "clientKey": {
+                        "type": "string"
+                      },
+                      "secret": {
+                        "type": "object",
+                        "required": [
+                          "name",
+                          "namespace"
+                        ],
+                        "properties": {
+                          "name": {
+                            "type": "string"
+                          },
+                          "namespace": {
+                            "type": "string"
+                          }
+                        }
+                      },
+                      "skipVerify": {
+                        "type": "boolean"
+                      }
+                    }
+                  },
+                  "uri": {
+                    "type": "string"
+                  }
+                }
+              },
               "mysql": {
                 "type": "object",
                 "required": [
@@ -4371,7 +4506,7 @@
                               "type": "object",
                               "properties": {
                                 "claims": {
-                                  "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable.",
+                                  "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers.",
                                   "type": "array",
                                   "items": {
                                     "description": "ResourceClaim references one entry in PodSpec.ResourceClaims.",
@@ -5567,7 +5702,7 @@
                               "type": "object",
                               "properties": {
                                 "claims": {
-                                  "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable.",
+                                  "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers.",
                                   "type": "array",
                                   "items": {
                                     "description": "ResourceClaim references one entry in PodSpec.ResourceClaims.",
@@ -6778,7 +6913,7 @@
                               "type": "object",
                               "properties": {
                                 "claims": {
-                                  "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable.",
+                                  "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers.",
                                   "type": "array",
                                   "items": {
                                     "description": "ResourceClaim references one entry in PodSpec.ResourceClaims.",
@@ -8035,7 +8170,7 @@
                                           "type": "object",
                                           "properties": {
                                             "claims": {
-                                              "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable.",
+                                              "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers.",
                                               "type": "array",
                                               "items": {
                                                 "description": "ResourceClaim references one entry in PodSpec.ResourceClaims.",
diff --git a/schemas/supportbundle-troubleshoot-v1beta2.json b/schemas/supportbundle-troubleshoot-v1beta2.json
index 5962bd68..63c4f147 100644
--- a/schemas/supportbundle-troubleshoot-v1beta2.json
+++ b/schemas/supportbundle-troubleshoot-v1beta2.json
@@ -1200,6 +1200,86 @@
                   }
                 }
               },
+              "mssql": {
+                "type": "object",
+                "required": [
+                  "collectorName",
+                  "outcomes"
+                ],
+                "properties": {
+                  "annotations": {
+                    "type": "object",
+                    "additionalProperties": {
+                      "type": "string"
+                    }
+                  },
+                  "checkName": {
+                    "type": "string"
+                  },
+                  "collectorName": {
+                    "type": "string"
+                  },
+                  "exclude": {
+                    "oneOf": [{"type": "string"},{"type": "boolean"}]
+                  },
+                  "fileName": {
+                    "type": "string"
+                  },
+                  "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"}]
+                  }
+                }
+              },
               "mysql": {
                 "type": "object",
                 "required": [
@@ -2697,6 +2777,61 @@
                   }
                 }
               },
+              "mssql": {
+                "type": "object",
+                "required": [
+                  "uri"
+                ],
+                "properties": {
+                  "collectorName": {
+                    "type": "string"
+                  },
+                  "exclude": {
+                    "oneOf": [{"type": "string"},{"type": "boolean"}]
+                  },
+                  "parameters": {
+                    "type": "array",
+                    "items": {
+                      "type": "string"
+                    }
+                  },
+                  "tls": {
+                    "type": "object",
+                    "properties": {
+                      "cacert": {
+                        "type": "string"
+                      },
+                      "clientCert": {
+                        "type": "string"
+                      },
+                      "clientKey": {
+                        "type": "string"
+                      },
+                      "secret": {
+                        "type": "object",
+                        "required": [
+                          "name",
+                          "namespace"
+                        ],
+                        "properties": {
+                          "name": {
+                            "type": "string"
+                          },
+                          "namespace": {
+                            "type": "string"
+                          }
+                        }
+                      },
+                      "skipVerify": {
+                        "type": "boolean"
+                      }
+                    }
+                  },
+                  "uri": {
+                    "type": "string"
+                  }
+                }
+              },
               "mysql": {
                 "type": "object",
                 "required": [
@@ -4417,7 +4552,7 @@
                               "type": "object",
                               "properties": {
                                 "claims": {
-                                  "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable.",
+                                  "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers.",
                                   "type": "array",
                                   "items": {
                                     "description": "ResourceClaim references one entry in PodSpec.ResourceClaims.",
@@ -5613,7 +5748,7 @@
                               "type": "object",
                               "properties": {
                                 "claims": {
-                                  "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable.",
+                                  "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers.",
                                   "type": "array",
                                   "items": {
                                     "description": "ResourceClaim references one entry in PodSpec.ResourceClaims.",
@@ -6824,7 +6959,7 @@
                               "type": "object",
                               "properties": {
                                 "claims": {
-                                  "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable.",
+                                  "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers.",
                                   "type": "array",
                                   "items": {
                                     "description": "ResourceClaim references one entry in PodSpec.ResourceClaims.",
@@ -8081,7 +8216,7 @@
                                           "type": "object",
                                           "properties": {
                                             "claims": {
-                                              "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable.",
+                                              "description": "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers.",
                                               "type": "array",
                                               "items": {
                                                 "description": "ResourceClaim references one entry in PodSpec.ResourceClaims.",


Test patch:
diff --git a/.github/workflows/build-test-deploy.yaml b/.github/workflows/build-test-deploy.yaml
index cf69b20b..122aa364 100644
--- a/.github/workflows/build-test-deploy.yaml
+++ b/.github/workflows/build-test-deploy.yaml
@@ -28,7 +28,7 @@ jobs:
   test:
     runs-on: ubuntu-20.04
     steps:
-      - uses: actions/setup-go@v3
+      - uses: actions/setup-go@v4
         with:
           go-version: "1.19"
 
@@ -45,7 +45,7 @@ jobs:
   test-integration:
     runs-on: ubuntu-20.04
     steps:
-      - uses: actions/setup-go@v3
+      - uses: actions/setup-go@v4
         with:
           go-version: "1.19"
 
@@ -66,7 +66,7 @@ jobs:
   ensure-schemas-are-generated:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/setup-go@v3
+      - uses: actions/setup-go@v4
         with:
           go-version: "1.19"
       - name: setup env
@@ -80,7 +80,7 @@ jobs:
   compile-preflight:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/setup-go@v3
+      - uses: actions/setup-go@v4
         with:
           go-version: "1.19"
       - name: setup env
@@ -156,7 +156,7 @@ jobs:
   compile-supportbundle:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/setup-go@v3
+      - uses: actions/setup-go@v4
         with:
           go-version: "1.19"
       - name: setup env
@@ -210,7 +210,7 @@ jobs:
   compile-collect:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/setup-go@v3
+      - uses: actions/setup-go@v4
         with:
           go-version: "1.19"
       - name: setup env
@@ -232,14 +232,16 @@ jobs:
       matrix:
         goarch: [amd64, arm64]
         goos: [darwin, linux, windows]
+        include:
+          - goarch: arm
+            goos: linux
     steps:
       - name: Checkout
         uses: actions/checkout@v3
+        with:
+          fetch-depth: 0
 
-      - name: Unshallow
-        run: git fetch --prune --unshallow
-
-      - uses: actions/setup-go@v3
+      - uses: actions/setup-go@v4
         with:
           go-version: "1.19"
 
@@ -262,16 +264,15 @@ jobs:
     steps:
       - name: Checkout
         uses: actions/checkout@v3
+        with:
+          fetch-depth: 0
 
       - uses: azure/docker-login@v1
         with:
           username: ${{ secrets.DOCKERHUB_USER }}
           password: ${{ secrets.DOCKERHUB_PASSWORD }}
 
-      - name: Unshallow
-        run: git fetch --prune --unshallow
-
-      - uses: actions/setup-go@v3
+      - uses: actions/setup-go@v4
         with:
           go-version: "1.19"
 
diff --git a/pkg/analyze/analyzer_test.go b/pkg/analyze/analyzer_test.go
index 59739664..1832cb73 100644
--- a/pkg/analyze/analyzer_test.go
+++ b/pkg/analyze/analyzer_test.go
@@ -72,3 +72,97 @@ func TestAnalyzeWithNilAnalyzer(t *testing.T) {
 	assert.Error(t, err)
 	assert.Nil(t, got)
 }
+
+func TestCollector_DedupCollectors(t *testing.T) {
+	tests := []struct {
+		name      string
+		Analyzers []*troubleshootv1beta2.Analyze
+		want      []*troubleshootv1beta2.Analyze
+	}{
+		{
+			name: "multiple ClusterVersion",
+			Analyzers: []*troubleshootv1beta2.Analyze{
+				{
+					ClusterVersion: &troubleshootv1beta2.ClusterVersion{},
+				},
+				{
+					ClusterVersion: &troubleshootv1beta2.ClusterVersion{},
+				},
+			},
+			want: []*troubleshootv1beta2.Analyze{
+				{
+					ClusterVersion: &troubleshootv1beta2.ClusterVersion{},
+				},
+			},
+		},
+		{
+			name: "multiple TextAnalyze",
+			Analyzers: []*troubleshootv1beta2.Analyze{
+				{
+					TextAnalyze: &troubleshootv1beta2.TextAnalyze{
+						AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
+							CheckName: "hi",
+						},
+					},
+				},
+				{
+					TextAnalyze: &troubleshootv1beta2.TextAnalyze{
+						AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
+							CheckName: "hi",
+						},
+					},
+				},
+			},
+			want: []*troubleshootv1beta2.Analyze{
+				{
+					TextAnalyze: &troubleshootv1beta2.TextAnalyze{
+						AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
+							CheckName: "hi",
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "multiple TextAnalyze with different CheckName",
+			Analyzers: []*troubleshootv1beta2.Analyze{
+				{
+					TextAnalyze: &troubleshootv1beta2.TextAnalyze{
+						AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
+							CheckName: "hi",
+						},
+					},
+				},
+				{
+					TextAnalyze: &troubleshootv1beta2.TextAnalyze{
+						AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
+							CheckName: "test",
+						},
+					},
+				},
+			},
+			want: []*troubleshootv1beta2.Analyze{
+				{
+					TextAnalyze: &troubleshootv1beta2.TextAnalyze{
+						AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
+							CheckName: "hi",
+						},
+					},
+				},
+				{
+					TextAnalyze: &troubleshootv1beta2.TextAnalyze{
+						AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
+							CheckName: "test",
+						},
+					},
+				},
+			},
+		},
+	}
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			got := DedupAnalyzers(tc.Analyzers)
+			assert.Equal(t, tc.want, got)
+		})
+	}
+}
diff --git a/pkg/analyze/host_block_devices_test.go b/pkg/analyze/host_block_devices_test.go
index ad5a239e..12df8485 100644
--- a/pkg/analyze/host_block_devices_test.go
+++ b/pkg/analyze/host_block_devices_test.go
@@ -158,7 +158,7 @@ func TestAnalyzeBlockDevices(t *testing.T) {
 			},
 		},
 		{
-			name: ".* > 1, pass with unmounted partition",
+			name: ".* == 1, pass with unmounted partition",
 			devices: []collect.BlockDeviceInfo{
 				{
 					Name:       "sdb",
@@ -175,13 +175,21 @@ func TestAnalyzeBlockDevices(t *testing.T) {
 					Major:            8,
 					Minor:            1,
 				},
+				{
+					Name:             "sdb2",
+					KernelName:       "sdb2",
+					ParentKernelName: "sdb",
+					Type:             "crypto_LUKS",
+					Major:            8,
+					Size:             1024 * 1024 * 1024 * 5,
+				},
 			},
 			hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
 				IncludeUnmountedPartitions: true,
 				Outcomes: []*troubleshootv1beta2.Outcome{
 					{
 						Pass: &troubleshootv1beta2.SingleOutcome{
-							When:    ".* >= 1",
+							When:    ".* == 1",
 							Message: "Block device or partition available",
 						},
 					},
@@ -255,11 +263,8 @@ func TestAnalyzeBlockDevices(t *testing.T) {
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			req := require.New(t)
 			b, err := json.Marshal(test.devices)
-			if err != nil {
-				t.Fatal(err)
-			}
+			require.NoError(t, err)
 
 			getCollectedFileContents := func(filename string) ([]byte, error) {
 				return b, nil
@@ -267,9 +272,9 @@ func TestAnalyzeBlockDevices(t *testing.T) {
 
 			result, err := (&AnalyzeHostBlockDevices{test.hostAnalyzer}).Analyze(getCollectedFileContents)
 			if test.expectErr {
-				req.Error(err)
+				assert.Error(t, err)
 			} else {
-				req.NoError(err)
+				assert.NoError(t, err)
 			}
 
 			assert.Equal(t, test.result, result)
diff --git a/pkg/analyze/mssql_test.go b/pkg/analyze/mssql_test.go
new file mode 100644
index 00000000..eb3b312d
--- /dev/null
+++ b/pkg/analyze/mssql_test.go
@@ -0,0 +1,117 @@
+package analyzer
+
+import (
+	"testing"
+
+	"github.com/replicatedhq/troubleshoot/pkg/collect"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_compareMssqlConditionalToActual(t *testing.T) {
+	tests := []struct {
+		name        string
+		conditional string
+		conn        collect.DatabaseConnection
+		hasError    bool
+		expect      bool
+	}{
+		{
+			name:        "Is Connected Succeeded",
+			conditional: "connected == true",
+			conn: collect.DatabaseConnection{
+				IsConnected: true,
+				Error:       "",
+				Version:     "",
+			},
+			hasError: false,
+			expect:   true,
+		},
+		{
+			name:        "Is Not Connected Succeeded",
+			conditional: "connected == false",
+			conn: collect.DatabaseConnection{
+				IsConnected: false,
+				Error:       "",
+				Version:     "",
+			},
+			hasError: false,
+			expect:   true,
+		},
+		{
+			name:        "Exact Match Version String Succeeded",
+			conditional: "version == 15.0.2000.1565",
+			conn: collect.DatabaseConnection{
+				IsConnected: true,
+				Error:       "",
+				Version:     "15.0.2000.1565",
+			},
+			hasError: false,
+			expect:   true,
+		},
+		{
+			name:        "Less Than Version Match Succeeded",
+			conditional: "version < 15.x",
+			conn: collect.DatabaseConnection{
+				IsConnected: true,
+				Error:       "",
+				Version:     "14.0.2000.0",
+			},
+			hasError: false,
+			expect:   true,
+		},
+		{
+			name:        "Inverse Less Than Version Match With Greater Than Version Succeeded",
+			conditional: "version > 15.x",
+			conn: collect.DatabaseConnection{
+				IsConnected: true,
+				Error:       "",
+				Version:     "14.0.2000.0",
+			},
+			hasError: false,
+			expect:   false,
+		},
+		{
+			name:        "Incorrect Conditional Provided Errors",
+			conditional: "",
+			conn:        collect.DatabaseConnection{},
+			hasError:    true,
+			expect:      false,
+		},
+		{
+			name:        "Four Part Version Wildcard Match Less Than Succeed",
+			conditional: "version < 15.0.2000.x",
+			conn: collect.DatabaseConnection{
+				IsConnected: true,
+				Error:       "",
+				Version:     "15.0.1999.0",
+			},
+			hasError: false,
+			expect:   true,
+		},
+		{
+			name:        "Four Part Version Wildcard Match Greater Than Succeed",
+			conditional: "version > 15.0.2000.x",
+			conn: collect.DatabaseConnection{
+				IsConnected: true,
+				Error:       "",
+				Version:     "15.0.2001.0",
+			},
+			hasError: false,
+			expect:   true,
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			req := require.New(t)
+			actual, err := compareMssqlConditionalToActual(test.conditional, &test.conn)
+			if test.hasError {
+				req.Error(err)
+			} else {
+				req.NoError(err)
+			}
+			assert.Equal(t, test.expect, actual)
+
+		})
+	}
+}
diff --git a/pkg/collect/mssql_test.go b/pkg/collect/mssql_test.go
new file mode 100644
index 00000000..1b145165
--- /dev/null
+++ b/pkg/collect/mssql_test.go
@@ -0,0 +1,64 @@
+package collect
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_parseMsSqlVersion(t *testing.T) {
+	tests := []struct {
+		name         string
+		mssqlVersion string
+		expect       string
+		hasError     bool
+	}{
+		{
+			name: "Valid String Succeed",
+			mssqlVersion: `Microsoft Azure SQL Edge Developer (RTM) - 15.0.2000.1565 (ARM64)
+			Jun 14 2022 00:37:12
+			Copyright (C) 2019 Microsoft Corporation
+			Linux (Ubuntu 18.04.6 LTS aarch64) <ARM64>`,
+			expect:   "15.0.2000.1565",
+			hasError: false,
+		},
+		{
+			name: "SemVer String Pass",
+			mssqlVersion: `Microsoft Azure SQL Edge Developer (RTM) - 15.0.1565 (ARM64)
+			Jun 14 2022 00:37:12
+			Copyright (C) 2019 Microsoft Corporation
+			Linux (Ubuntu 18.04.6 LTS aarch64) <ARM64>`,
+			expect:   "15.0.1565",
+			hasError: false,
+		},
+		{
+			name: "Missing SQL Fail",
+			mssqlVersion: `Microsoft Azure Edge Developer (RTM) - 15.1.2020.1565 (ARM64)
+			Jun 14 2022 00:37:12
+			Copyright (C) 2019 Microsoft Corporation
+			Linux (Ubuntu 18.04.6 LTS aarch64) <ARM64>`,
+			expect:   "",
+			hasError: true,
+		},
+		{
+			name:         "Empty String Fail",
+			mssqlVersion: "",
+			expect:       "",
+			hasError:     true,
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			req := require.New(t)
+			actual, err := parseMsSqlVersion(test.mssqlVersion)
+			if test.hasError {
+				req.Error(err)
+			} else {
+				req.NoError(err)
+			}
+			assert.Equal(t, test.expect, actual)
+
+		})
+	}
+}