Deployment and Statefulset status analyzers

This commit is contained in:
Marc Campbell
2019-11-07 01:54:32 +00:00
parent c0415dd100
commit 6e7715a145
7 changed files with 811 additions and 0 deletions

View File

@@ -37,6 +37,12 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
if analyzer.ImagePullSecret != nil {
return analyzeImagePullSecret(analyzer.ImagePullSecret, findFiles)
}
if analyzer.DeploymentStatus != nil {
return deploymentStatus(analyzer.DeploymentStatus, getFile)
}
if analyzer.StatefulsetStatus != nil {
return statefulsetStatus(analyzer.StatefulsetStatus, getFile)
}
return nil, errors.New("invalid analyzer")
}

454
pkg/analyze/data_test.go Normal file
View File

@@ -0,0 +1,454 @@
package analyzer
var collectedDeployments = `[
{
"metadata": {
"name": "kotsadm-api",
"namespace": "default",
"selfLink": "/apis/apps/v1/namespaces/default/deployments/kotsadm-api",
"uid": "56526035-cd29-4d08-8375-291503b1a006",
"resourceVersion": "1583068",
"generation": 1,
"creationTimestamp": "2019-11-07T00:34:32Z",
"labels": {
"app.kubernetes.io/managed-by": "skaffold-v0.41.0"
},
"annotations": {
"deployment.kubernetes.io/revision": "1",
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"annotations\":{},\"labels\":{\"app.kubernetes.io/managed-by\":\"skaffold-v0.41.0\",\"skaffold.dev/builder\":\"local\",\"skaffold.dev/cleanup\":\"true\",\"skaffold.dev/deployer\":\"kustomize\",\"skaffold.dev/docker-api-version\":\"1.40\",\"skaffold.dev/run-id\":\"98f0a02b-9739-4d94-ba11-3e4d273c743e\",\"skaffold.dev/tag-policy\":\"git-commit\",\"skaffold.dev/tail\":\"true\"},\"name\":\"kotsadm-api\",\"namespace\":\"default\"},\"spec\":{\"selector\":{\"matchLabels\":{\"app\":\"kotsadm-api\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"kotsadm-api\",\"app.kubernetes.io/managed-by\":\"skaffold-v0.41.0\",\"skaffold.dev/builder\":\"local\",\"skaffold.dev/cleanup\":\"true\",\"skaffold.dev/deployer\":\"kustomize\",\"skaffold.dev/docker-api-version\":\"1.40\",\"skaffold.dev/run-id\":\"98f0a02b-9739-4d94-ba11-3e4d273c743e\",\"skaffold.dev/tag-policy\":\"git-commit\",\"skaffold.dev/tail\":\"true\"}},\"spec\":{\"affinity\":{\"podAffinity\":{\"preferredDuringSchedulingIgnoredDuringExecution\":[{\"podAffinityTerm\":{\"labelSelector\":{\"matchExpressions\":[{\"key\":\"app\",\"operator\":\"In\",\"values\":[\"ship-www\"]}]},\"topologyKey\":\"kubernetes.io/hostname\"},\"weight\":1}]},\"podAntiAffinity\":{\"preferredDuringSchedulingIgnoredDuringExecution\":[{\"podAffinityTerm\":{\"labelSelector\":{\"matchExpressions\":[{\"key\":\"app\",\"operator\":\"In\",\"values\":[\"kotsadm-api\"]}]},\"topologyKey\":\"kubernetes.io/hostname\"},\"weight\":2}]}},\"containers\":[{\"env\":[{\"name\":\"DEV_NAMESPACE\",\"value\":\"test\"},{\"name\":\"LOG_LEVEL\",\"value\":\"debug\"},{\"name\":\"SESSION_KEY\",\"valueFrom\":{\"secretKeyRef\":{\"key\":\"key\",\"name\":\"session\"}}},{\"name\":\"POSTGRES_URI\",\"valueFrom\":{\"secretKeyRef\":{\"key\":\"uri\",\"name\":\"ship-postgres\"}}},{\"name\":\"API_ENCRYPTION_KEY\",\"value\":\"IvWItkB8+ezMisPjSMBknT1PdKjBx7Xc/txZqOP8Y2Oe7+Jy\"},{\"name\":\"INIT_SERVER_URI\",\"value\":\"http://init-server:3000\"},{\"name\":\"WATCH_SERVER_URI\",\"value\":\"http://watch-server:3000\"},{\"name\":\"PINO_LOG_PRETTY\",\"value\":\"1\"},{\"name\":\"S3_BUCKET_NAME\",\"value\":\"shipbucket\"},{\"name\":\"AIRGAP_BUNDLE_S3_BUCKET\",\"value\":\"airgap\"},{\"name\":\"S3_ENDPOINT\",\"value\":\"http://kotsadm-s3.default.svc.cluster.local:4569/\"},{\"name\":\"S3_ACCESS_KEY_ID\",\"value\":\"***HIDDEN***\"},{\"name\":\"S3_SECRET_ACCESS_KEY\",\"value\":\"***HIDDEN***\"},{\"name\":\"S3_BUCKET_ENDPOINT\",\"value\":\"true\"},{\"name\":\"GITHUB_CLIENT_ID\",\"valueFrom\":{\"secretKeyRef\":{\"key\":\"client-id\",\"name\":\"github-app\"}}},{\"name\":\"GITHUB_CLIENT_SECRET\",\"valueFrom\":{\"secretKeyRef\":{\"key\":\"client-secret\",\"name\":\"github-app\"}}},{\"name\":\"GITHUB_INTEGRATION_ID\",\"valueFrom\":{\"secretKeyRef\":{\"key\":\"integration-id\",\"name\":\"github-app\"}}},{\"name\":\"GITHUB_PRIVATE_KEY_FILE\",\"value\":\"/keys/github/private-key.pem\"},{\"name\":\"SHIP_API_ENDPOINT\",\"value\":\"http://kotsadm-api.default.svc.cluster.local:3000\"},{\"name\":\"SHIP_API_ADVERTISE_ENDPOINT\",\"value\":\"http://localhost:30065\"},{\"name\":\"GRAPHQL_PREM_ENDPOINT\",\"value\":\"http://graphql-api-prem:3000/graphql\"},{\"name\":\"AUTO_CREATE_CLUSTER\",\"value\":\"1\"},{\"name\":\"AUTO_CREATE_CLUSTER_NAME\",\"value\":\"microk8s\"},{\"name\":\"AUTO_CREATE_CLUSTER_TOKEN\",\"value\":\"***HIDDEN***\"},{\"name\":\"ENABLE_SHIP\",\"value\":\"1\"},{\"name\":\"ENABLE_KOTS\",\"value\":\"1\"},{\"name\":\"ENABLE_KURL\",\"value\":\"1\"},{\"name\":\"POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}}],\"image\":\"localhost:32000/kotsadm-api:v1.0.1-30-g8fa13e34-dirty@sha256:4a0ca1a2eae46472bd2d454f9e763a458ddd172689e179b17054262507bb4fc8\",\"imagePullPolicy\":\"IfNotPresent\",\"name\":\"kotsadm-api\",\"ports\":[{\"containerPort\":3000,\"name\":\"http\"},{\"containerPort\":9229,\"name\":\"debug\"}],\"readinessProbe\":{\"httpGet\":{\"path\":\"/healthz\",\"port\":3000},\"initialDelaySeconds\":2,\"periodSeconds\":2},\"volumeMounts\":[{\"mountPath\":\"/keys/github\",\"name\":\"github-app-private-key\",\"readOnly\":true}]}],\"restartPolicy\":\"Always\",\"securityContext\":{\"runAsUser\":0},\"serviceAccount\":\"kotsadm-api\",\"volumes\":[{\"name\":\"github-app-private-key\",\"secret\":{\"secretName\":\"github-app-private-key\"}}]}}}}\n"
}
},
"spec": {
"replicas": 1,
"selector": {
"matchLabels": {
"app": "kotsadm-api"
}
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"app": "kotsadm-api"
}
},
"spec": {
"containers": [
{
"name": "kotsadm-api",
"image": "localhost:32000/kotsadm-api:v1.0.1-30-g8fa13e34-dirty@sha256:4a0ca1a2eae46472bd2d454f9e763a458ddd172689e179b17054262507bb4fc8",
"ports": [
{
"name": "http",
"containerPort": 3000,
"protocol": "TCP"
}
],
"env": [
{
"name": "DEV_NAMESPACE",
"value": "test"
},
{
"name": "POD_NAMESPACE",
"valueFrom": {
"fieldRef": {
"apiVersion": "v1",
"fieldPath": "metadata.namespace"
}
}
}
],
"resources": {},
"readinessProbe": {
"httpGet": {
"path": "/healthz",
"port": 3000,
"scheme": "HTTP"
},
"initialDelaySeconds": 2,
"timeoutSeconds": 1,
"periodSeconds": 2,
"successThreshold": 1,
"failureThreshold": 3
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"imagePullPolicy": "IfNotPresent"
}
],
"restartPolicy": "Always",
"terminationGracePeriodSeconds": 30,
"dnsPolicy": "ClusterFirst",
"serviceAccountName": "kotsadm-api",
"serviceAccount": "kotsadm-api",
"schedulerName": "default-scheduler"
}
},
"strategy": {
"type": "RollingUpdate",
"rollingUpdate": {
"maxUnavailable": "25%",
"maxSurge": "25%"
}
},
"revisionHistoryLimit": 10,
"progressDeadlineSeconds": 600
},
"status": {
"observedGeneration": 1,
"replicas": 1,
"updatedReplicas": 1,
"readyReplicas": 1,
"availableReplicas": 1,
"conditions": [
{
"type": "Available",
"status": "True",
"lastUpdateTime": "2019-11-07T00:35:23Z",
"lastTransitionTime": "2019-11-07T00:35:23Z",
"reason": "MinimumReplicasAvailable",
"message": "Deployment has minimum availability."
},
{
"type": "Progressing",
"status": "True",
"lastUpdateTime": "2019-11-07T00:35:23Z",
"lastTransitionTime": "2019-11-07T00:34:32Z",
"reason": "NewReplicaSetAvailable",
"message": "ReplicaSet \"kotsadm-api-6f4b994bd5\" has successfully progressed."
}
]
}
},
{
"metadata": {
"name": "kotsadm-operator",
"namespace": "default",
"selfLink": "/apis/apps/v1/namespaces/default/deployments/kotsadm-operator",
"uid": "cfae9877-eef4-44c9-acac-0bf0d1aa547e",
"resourceVersion": "1583379",
"generation": 2,
"creationTimestamp": "2019-11-07T00:34:32Z",
"labels": {
"app.kubernetes.io/managed-by": "skaffold-v0.41.0"
},
"annotations": {
"deployment.kubernetes.io/revision": "2",
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"annotations\":{},\"labels\":{\"app.kubernetes.io/managed-by\":\"skaffold-v0.41.0\",\"skaffold.dev/builder\":\"local\",\"skaffold.dev/cleanup\":\"true\",\"skaffold.dev/deployer\":\"kustomize\",\"skaffold.dev/docker-api-version\":\"1.40\",\"skaffold.dev/run-id\":\"98f0a02b-9739-4d94-ba11-3e4d273c743e\",\"skaffold.dev/tag-policy\":\"git-commit\",\"skaffold.dev/tail\":\"true\"},\"name\":\"kotsadm-operator\",\"namespace\":\"default\"},\"spec\":{\"selector\":{\"matchLabels\":{\"app\":\"kotsadm-operator\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"kotsadm-operator\",\"app.kubernetes.io/managed-by\":\"skaffold-v0.41.0\",\"skaffold.dev/builder\":\"local\",\"skaffold.dev/cleanup\":\"true\",\"skaffold.dev/deployer\":\"kustomize\",\"skaffold.dev/docker-api-version\":\"1.40\",\"skaffold.dev/run-id\":\"98f0a02b-9739-4d94-ba11-3e4d273c743e\",\"skaffold.dev/tag-policy\":\"git-commit\",\"skaffold.dev/tail\":\"true\"}},\"spec\":{\"containers\":[{\"env\":[{\"name\":\"KOTSADM_API_ENDPOINT\",\"value\":\"http://kotsadm-api:3000\"},{\"name\":\"KOTSADM_TOKEN\",\"value\":\"***HIDDEN***\"},{\"name\":\"KOTSADM_TARGET_NAMESPACE\",\"value\":\"test\"}],\"image\":\"localhost:32000/kotsadm-operator:v1.0.1-30-g8fa13e34-dirty@sha256:177c15b6399717048e9355bc8fd8b8ed213be90615c7c6ee7b7fdcee50aca6c5\",\"imagePullPolicy\":\"Always\",\"name\":\"kotsadm-operator\",\"resources\":{\"limits\":{\"cpu\":\"200m\",\"memory\":\"1000Mi\"},\"requests\":{\"cpu\":\"100m\",\"memory\":\"500Mi\"}}}],\"restartPolicy\":\"Always\"}}}}\n"
}
},
"spec": {
"replicas": 1,
"selector": {
"matchLabels": {
"app": "kotsadm-operator"
}
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"app": "kotsadm-operator"
}
},
"spec": {
"containers": [
{
"name": "kotsadm-operator",
"image": "localhost:32000/kotsadm-operator:v1.0.1-30-g8fa13e34-dirty@sha256:177c15b6399717048e9355bc8fd8b8ed213be90615c7c6ee7b7fdcee50aca6c5",
"env": [
{
"name": "KOTSADM_API_ENDPOINT",
"value": "http://kotsadm-api:3000"
},
{
"name": "KOTSADM_TOKEN",
"value": "***HIDDEN***"
},
{
"name": "KOTSADM_TARGET_NAMESPACE",
"value": "test"
}
],
"resources": {
"limits": {
"cpu": "200m",
"memory": "1000Mi"
},
"requests": {
"cpu": "100m",
"memory": "500Mi"
}
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"imagePullPolicy": "Always"
}
],
"restartPolicy": "Always",
"terminationGracePeriodSeconds": 30,
"dnsPolicy": "ClusterFirst",
"securityContext": {},
"schedulerName": "default-scheduler"
}
},
"strategy": {
"type": "RollingUpdate",
"rollingUpdate": {
"maxUnavailable": "25%",
"maxSurge": "25%"
}
},
"revisionHistoryLimit": 10,
"progressDeadlineSeconds": 600
},
"status": {
"observedGeneration": 2,
"replicas": 1,
"updatedReplicas": 1,
"readyReplicas": 1,
"availableReplicas": 1,
"conditions": [
{
"type": "Available",
"status": "True",
"lastUpdateTime": "2019-11-07T00:34:37Z",
"lastTransitionTime": "2019-11-07T00:34:37Z",
"reason": "MinimumReplicasAvailable",
"message": "Deployment has minimum availability."
},
{
"type": "Progressing",
"status": "True",
"lastUpdateTime": "2019-11-07T00:36:34Z",
"lastTransitionTime": "2019-11-07T00:34:32Z",
"reason": "NewReplicaSetAvailable",
"message": "ReplicaSet \"kotsadm-operator-5b5c977699\" has successfully progressed."
}
]
}
},
{
"metadata": {
"name": "kotsadm-postgres-watch",
"namespace": "default",
"selfLink": "/apis/apps/v1/namespaces/default/deployments/kotsadm-postgres-watch",
"uid": "ec195b07-4fd2-4bbe-8b01-4cbdfbe0e79d",
"resourceVersion": "1582762",
"generation": 1,
"creationTimestamp": "2019-11-07T00:34:39Z",
"annotations": {
"deployment.kubernetes.io/revision": "1"
},
"ownerReferences": [
{
"apiVersion": "databases.schemahero.io/v1alpha2",
"kind": "Database",
"name": "kotsadm-postgres",
"uid": "e6d51ce6-c4c1-428f-9e8e-56f1a3a43588",
"controller": true,
"blockOwnerDeletion": true
}
]
},
"spec": {
"replicas": 1,
"selector": {
"matchLabels": {
"deployment": "kotsadm-postgreswatch"
}
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"deployment": "kotsadm-postgreswatch"
}
},
"spec": {
"containers": [
{
"name": "schemahero",
"image": "schemahero/schemahero:alpha",
"args": [
"watch",
"--driver",
"postgres",
"--uri",
"postgres://shipcloud:password@postgres.default.svc.cluster.local:5432/shipcloud?sslmode=disable",
"--namespace",
"default",
"--instance",
"kotsadm-postgres"
],
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"imagePullPolicy": "Always"
}
],
"restartPolicy": "Always",
"terminationGracePeriodSeconds": 30,
"dnsPolicy": "ClusterFirst",
"serviceAccountName": "kotsadm-postgres",
"serviceAccount": "kotsadm-postgres",
"securityContext": {},
"schedulerName": "default-scheduler"
}
},
"strategy": {
"type": "RollingUpdate",
"rollingUpdate": {
"maxUnavailable": "25%",
"maxSurge": "25%"
}
},
"revisionHistoryLimit": 10,
"progressDeadlineSeconds": 600
},
"status": {
"observedGeneration": 1,
"replicas": 1,
"updatedReplicas": 1,
"readyReplicas": 1,
"availableReplicas": 1,
"conditions": [
{
"type": "Available",
"status": "True",
"lastUpdateTime": "2019-11-07T00:35:00Z",
"lastTransitionTime": "2019-11-07T00:35:00Z",
"reason": "MinimumReplicasAvailable",
"message": "Deployment has minimum availability."
},
{
"type": "Progressing",
"status": "True",
"lastUpdateTime": "2019-11-07T00:35:00Z",
"lastTransitionTime": "2019-11-07T00:34:39Z",
"reason": "NewReplicaSetAvailable",
"message": "ReplicaSet \"kotsadm-postgres-watch-5cf76f4c45\" has successfully progressed."
}
]
}
},
{
"metadata": {
"name": "kotsadm-web",
"namespace": "default",
"selfLink": "/apis/apps/v1/namespaces/default/deployments/kotsadm-web",
"uid": "4e657f8a-5edb-498b-9402-1f93f51f5dda",
"resourceVersion": "1582354",
"generation": 1,
"creationTimestamp": "2019-11-07T00:34:32Z",
"labels": {
"app.kubernetes.io/managed-by": "skaffold-v0.41.0",
"skaffold.dev/builder": "local",
"skaffold.dev/cleanup": "true",
"skaffold.dev/deployer": "kustomize",
"skaffold.dev/docker-api-version": "1.40",
"skaffold.dev/run-id": "98f0a02b-9739-4d94-ba11-3e4d273c743e",
"skaffold.dev/tag-policy": "git-commit",
"skaffold.dev/tail": "true"
},
"annotations": {
"deployment.kubernetes.io/revision": "1",
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"annotations\":{},\"labels\":{\"app.kubernetes.io/managed-by\":\"skaffold-v0.41.0\",\"skaffold.dev/builder\":\"local\",\"skaffold.dev/cleanup\":\"true\",\"skaffold.dev/deployer\":\"kustomize\",\"skaffold.dev/docker-api-version\":\"1.40\",\"skaffold.dev/run-id\":\"98f0a02b-9739-4d94-ba11-3e4d273c743e\",\"skaffold.dev/tag-policy\":\"git-commit\",\"skaffold.dev/tail\":\"true\"},\"name\":\"kotsadm-web\",\"namespace\":\"default\"},\"spec\":{\"selector\":{\"matchLabels\":{\"app\":\"kotsadm-web\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"kotsadm-web\",\"app.kubernetes.io/managed-by\":\"skaffold-v0.41.0\",\"skaffold.dev/builder\":\"local\",\"skaffold.dev/cleanup\":\"true\",\"skaffold.dev/deployer\":\"kustomize\",\"skaffold.dev/docker-api-version\":\"1.40\",\"skaffold.dev/run-id\":\"98f0a02b-9739-4d94-ba11-3e4d273c743e\",\"skaffold.dev/tag-policy\":\"git-commit\",\"skaffold.dev/tail\":\"true\"}},\"spec\":{\"containers\":[{\"env\":[{\"name\":\"GITHUB_CLIENT_ID\",\"valueFrom\":{\"secretKeyRef\":{\"key\":\"client-id\",\"name\":\"github-app\"}}},{\"name\":\"GITHUB_INSTALL_URL\",\"valueFrom\":{\"secretKeyRef\":{\"key\":\"install-url\",\"name\":\"github-app\"}}},{\"name\":\"SHIP_CLUSTER_API_SERVER\",\"value\":\"http://localhost:30065\"},{\"name\":\"SHIP_CLUSTER_WEB_URI\",\"value\":\"http://localhost:8000\"}],\"image\":\"localhost:32000/kotsadm-web:v1.0.1-30-g8fa13e34@sha256:5b5b5b640b6e09d8b3185d4ae15ac4dc558d4e2ea034ac3e567d8cce04eadb9c\",\"imagePullPolicy\":\"IfNotPresent\",\"name\":\"kotsadm-web\",\"ports\":[{\"containerPort\":8000,\"name\":\"http\"}]}]}}}}\n"
}
},
"spec": {
"replicas": 1,
"selector": {
"matchLabels": {
"app": "kotsadm-web"
}
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"app": "kotsadm-web",
"app.kubernetes.io/managed-by": "skaffold-v0.41.0",
"skaffold.dev/builder": "local",
"skaffold.dev/cleanup": "true",
"skaffold.dev/deployer": "kustomize",
"skaffold.dev/docker-api-version": "1.40",
"skaffold.dev/run-id": "98f0a02b-9739-4d94-ba11-3e4d273c743e",
"skaffold.dev/tag-policy": "git-commit",
"skaffold.dev/tail": "true"
}
},
"spec": {
"containers": [
{
"name": "kotsadm-web",
"image": "localhost:32000/kotsadm-web:v1.0.1-30-g8fa13e34@sha256:5b5b5b640b6e09d8b3185d4ae15ac4dc558d4e2ea034ac3e567d8cce04eadb9c",
"ports": [
{
"name": "http",
"containerPort": 8000,
"protocol": "TCP"
}
],
"env": [
{
"name": "GITHUB_CLIENT_ID",
"valueFrom": {
"secretKeyRef": {
"name": "github-app",
"key": "client-id"
}
}
}
],
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"imagePullPolicy": "IfNotPresent"
}
],
"restartPolicy": "Always",
"terminationGracePeriodSeconds": 30,
"dnsPolicy": "ClusterFirst",
"securityContext": {},
"schedulerName": "default-scheduler"
}
},
"strategy": {
"type": "RollingUpdate",
"rollingUpdate": {
"maxUnavailable": "25%",
"maxSurge": "25%"
}
},
"revisionHistoryLimit": 10,
"progressDeadlineSeconds": 600
},
"status": {
"observedGeneration": 1,
"replicas": 1,
"updatedReplicas": 1,
"readyReplicas": 1,
"availableReplicas": 1,
"conditions": [
{
"type": "Available",
"status": "True",
"lastUpdateTime": "2019-11-07T00:34:39Z",
"lastTransitionTime": "2019-11-07T00:34:39Z",
"reason": "MinimumReplicasAvailable",
"message": "Deployment has minimum availability."
},
{
"type": "Progressing",
"status": "True",
"lastUpdateTime": "2019-11-07T00:34:39Z",
"lastTransitionTime": "2019-11-07T00:34:32Z",
"reason": "NewReplicaSetAvailable",
"message": "ReplicaSet \"kotsadm-web-79bfb95c48\" has successfully progressed."
}
]
}
}
]`

View File

@@ -0,0 +1,153 @@
package analyzer
import (
"encoding/json"
"fmt"
"path"
"strconv"
"strings"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
appsv1 "k8s.io/api/apps/v1"
)
func deploymentStatus(analyzer *troubleshootv1beta1.DeploymentStatus, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
collected, err := getCollectedFileContents(path.Join("cluster-resources", "deployments", fmt.Sprintf("%s.json", analyzer.Namespace)))
if err != nil {
return nil, errors.Wrap(err, "failed to read collected deployments from namespace")
}
var deployments []appsv1.Deployment
if err := json.Unmarshal(collected, &deployments); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal deployment list")
}
var status *appsv1.DeploymentStatus
if analyzer.Name != "" {
for _, deployment := range deployments {
if deployment.Name == analyzer.Name {
status = &deployment.Status
}
}
} else if analyzer.Selector != nil {
}
if status == nil {
// there's not an error, but maybe the requested deployment is not even deployed
return &AnalyzeResult{
IsFail: true,
Message: "not found",
}, nil
}
result := &AnalyzeResult{}
// ordering from the spec is important, the first one that matches returns
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
}
match, err := compareActualToWhen(outcome.Fail.When, int(status.ReadyReplicas))
if err != nil {
return nil, errors.Wrap(err, "failed to parse fail range")
}
if match {
result.IsFail = true
result.Message = outcome.Fail.Message
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
}
match, err := compareActualToWhen(outcome.Warn.When, int(status.ReadyReplicas))
if err != nil {
return nil, errors.Wrap(err, "failed to parse warn range")
}
if match {
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
}
match, err := compareActualToWhen(outcome.Pass.When, int(status.ReadyReplicas))
if err != nil {
return nil, errors.Wrap(err, "failed to parse pass range")
}
if match {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return result, nil
}
}
}
return result, nil
}
func compareActualToWhen(when string, actual int) (bool, error) {
parts := strings.Split(strings.TrimSpace(when), " ")
// we can make this a lot more flexible
if len(parts) != 2 {
return false, errors.New("unable to parse when range")
}
value, err := strconv.Atoi(parts[1])
if err != nil {
return false, errors.New("unable to parse when value")
}
switch parts[0] {
case "=":
fallthrough
case "==":
fallthrough
case "===":
return actual == value, nil
case "<":
return actual < value, nil
case ">":
return actual > value, nil
case "<=":
return actual <= value, nil
case ">=":
return actual >= value, nil
}
return false, errors.Errorf("unknown comparator: %q", parts[0])
}

View File

@@ -0,0 +1,95 @@
package analyzer
import (
"testing"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_deploymentStatus(t *testing.T) {
tests := []struct {
name string
analyzer troubleshootv1beta1.DeploymentStatus
expectResult AnalyzeResult
files map[string][]byte
}{
{
name: "1/1, = 1",
analyzer: troubleshootv1beta1.DeploymentStatus{
Outcomes: []*troubleshootv1beta1.Outcome{
{
Pass: &troubleshootv1beta1.SingleOutcome{
When: "= 1",
Message: "pass",
},
},
{
Fail: &troubleshootv1beta1.SingleOutcome{
Message: "fail",
},
},
},
Namespace: "default",
Name: "kotsadm-api",
},
expectResult: AnalyzeResult{
IsPass: true,
IsWarn: false,
IsFail: false,
Title: "",
Message: "pass",
},
files: map[string][]byte{
"cluster-resources/deployments/default.json": []byte(collectedDeployments),
},
},
{
name: "1/1, = 2",
analyzer: troubleshootv1beta1.DeploymentStatus{
Outcomes: []*troubleshootv1beta1.Outcome{
{
Pass: &troubleshootv1beta1.SingleOutcome{
When: "= 2",
Message: "pass",
},
},
{
Fail: &troubleshootv1beta1.SingleOutcome{
Message: "fail",
},
},
},
Namespace: "default",
Name: "kotsadm-api",
},
expectResult: AnalyzeResult{
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "",
Message: "fail",
},
files: map[string][]byte{
"cluster-resources/deployments/default.json": []byte(collectedDeployments),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
getFiles := func(n string) ([]byte, error) {
return test.files[n], nil
}
actual, err := deploymentStatus(&test.analyzer, getFiles)
req.NoError(err)
assert.Equal(t, &test.expectResult, actual)
})
}
}

View File

@@ -0,0 +1,11 @@
package analyzer
import (
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
)
func statefulsetStatus(analyzer *troubleshootv1beta1.StatefulsetStatus, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
return nil, errors.New("not implemented")
}

View File

@@ -50,6 +50,22 @@ type ImagePullSecret struct {
RegistryName string `json:"registryName" yaml:"registryName"`
}
type DeploymentStatus struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
Namespace string `json:"namespace" yaml:"namespace"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Selector []string `json:"selector,omitempty" yaml:"selector,omitempty"`
}
type StatefulsetStatus struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
Namespace string `json:"namespace" yaml:"namespace"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Selector []string `json:"selector,omitempty" yaml:"selector,omitempty"`
}
type AnalyzeMeta struct {
CheckName string `json:"checkName,omitempty" yaml:"checkName,omitempty"`
}
@@ -61,4 +77,6 @@ type Analyze struct {
Ingress *Ingress `json:"ingress,omitempty" yaml:"ingress,omitempty"`
Secret *AnalyzeSecret `json:"secret,omitempty" yaml:"secret,omitempty"`
ImagePullSecret *ImagePullSecret `json:"imagePullSecret,omitempty" yaml:"imagePullSecret,omitempty"`
DeploymentStatus *DeploymentStatus `json:"deploymentStatus,omitempty" yaml:"deploymentStatus,omitempty"`
StatefulsetStatus *StatefulsetStatus `json:"statefulsetStatus,omitempty" yaml:"statefulsetStatus,omitempty"`
}

View File

@@ -82,6 +82,16 @@ func (in *Analyze) DeepCopyInto(out *Analyze) {
*out = new(ImagePullSecret)
(*in).DeepCopyInto(*out)
}
if in.DeploymentStatus != nil {
in, out := &in.DeploymentStatus, &out.DeploymentStatus
*out = new(DeploymentStatus)
(*in).DeepCopyInto(*out)
}
if in.StatefulsetStatus != nil {
in, out := &in.StatefulsetStatus, &out.StatefulsetStatus
*out = new(StatefulsetStatus)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Analyze.
@@ -731,6 +741,38 @@ func (in *CustomResourceDefinition) DeepCopy() *CustomResourceDefinition {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DeploymentStatus) DeepCopyInto(out *DeploymentStatus) {
*out = *in
out.AnalyzeMeta = in.AnalyzeMeta
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
if in.Selector != nil {
in, out := &in.Selector, &out.Selector
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentStatus.
func (in *DeploymentStatus) DeepCopy() *DeploymentStatus {
if in == nil {
return nil
}
out := new(DeploymentStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Exec) DeepCopyInto(out *Exec) {
*out = *in
@@ -1307,6 +1349,38 @@ func (in *SingleOutcome) DeepCopy() *SingleOutcome {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *StatefulsetStatus) DeepCopyInto(out *StatefulsetStatus) {
*out = *in
out.AnalyzeMeta = in.AnalyzeMeta
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
if in.Selector != nil {
in, out := &in.Selector, &out.Selector
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatefulsetStatus.
func (in *StatefulsetStatus) DeepCopy() *StatefulsetStatus {
if in == nil {
return nil
}
out := new(StatefulsetStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *StorageClass) DeepCopyInto(out *StorageClass) {
*out = *in