I've faced with the same task and take k8s.io/kubernetes/pkg/apis/apps/validation
and k8s.io/kubernetes/pkg/apis/core/validation
using this script:
#!/bin/sh
set -euo pipefail
VERSION=${1#"v"}
if [ -z "$VERSION" ]; then
echo "Must specify version!"
exit 1
fi
MODS=($(
curl -sS https://raw.githubusercontent.com/kubernetes/kubernetes/v${VERSION}/go.mod |
sed -n 's|.*k8s.io/\(.*\) => ./staging/src/k8s.io/.*|k8s.io/\1|p'
))
for MOD in "${MODS[@]}"; do
V=$(
go mod download -json "${MOD}@kubernetes-${VERSION}" |
sed -n 's|.*"Version": "\(.*\)".*|\1|p'
)
go mod edit "-replace=${MOD}=${MOD}@${V}"
done
go get "k8s.io/kubernetes@v${VERSION}"
Than you can write a test like this to validate manifest with a Service
and Deployment
:
import (
"github.com/stretchr/testify/assert"
appsV1 "k8s.io/api/apps/v1"
coreV1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes/scheme"
kubernetesApps "k8s.io/kubernetes/pkg/apis/apps"
kubernetesAppsV1 "k8s.io/kubernetes/pkg/apis/apps/v1"
kubernetesAppsValidation "k8s.io/kubernetes/pkg/apis/apps/validation"
kubernetesCore "k8s.io/kubernetes/pkg/apis/core"
kubernetesCoreV1 "k8s.io/kubernetes/pkg/apis/core/v1"
kubernetesCoreValidation "k8s.io/kubernetes/pkg/apis/core/validation"
"strings"
"testing"
"text/template"
)
const RedisTemplate = `
---
apiVersion: v1
kind: Service
metadata:
name: redis
spec:
type: ClusterIP
internalTrafficPolicy: Cluster
sessionAffinity: None
ports:
- name: redis-port
protocol: TCP
port: 6379
targetPort: 6379
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
....
`
func Test_RedisManifest(t *testing.T) {
for _, m := range strings.Split(RedisTemplate, "---") {
if len(strings.Trim(m, "\n")) == 0 {
continue
}
obj, _, err := scheme.Codecs.UniversalDeserializer().Decode([]byte(m), nil, nil)
assert.NoError(t, err)
assert.NotEmptyf(t, obj.GetObjectKind().GroupVersionKind().Kind, "parsed k8s object kind is empty")
if service, ok := obj.(*coreV1.Service); ok {
kubernetesService := &kubernetesCore.Service{}
conversionErr := kubernetesCoreV1.Convert_v1_Service_To_core_Service(service, kubernetesService, nil)
assert.NoError(t, conversionErr)
kubernetesService.ObjectMeta.Namespace = "default"
validationErrs := kubernetesCoreValidation.ValidateService(kubernetesService)
assert.Empty(t, validationErrs)
}
if deployment, ok := obj.(*appsV1.Deployment); ok {
kubernetesDeployment := &kubernetesApps.Deployment{}
conversionErr := kubernetesAppsV1.Convert_v1_Deployment_To_apps_Deployment(deployment, kubernetesDeployment, nil)
assert.NoError(t, conversionErr)
kubernetesDeployment.ObjectMeta.Namespace = "default"
validationErrs := kubernetesAppsValidation.ValidateDeployment(kubernetesDeployment, kubernetesCoreValidation.PodValidationOptions{})
assert.Empty(t, validationErrs)
}
}
}
This approach really helps to find cases like spec.template.spec.containers[0].ports[0].name: Invalid value: "something-redis-port": must be no more than 15 characters
which is great, but there several disadvantages:
- Direct import of
k8s.io/kubernetes
is not recommended by K8s team.
- Direct import of
k8s.io/kubernetes
can create a troubles in the future.
- Validation funcs
ValidateService
and ValidateDeployment
are expecting that optional fields like namespace
, internalTrafficPolicy
, sessionAffinity
and a lot of others must have values. Maybe there some other funcs which can fill optional fields with default values but I've not found them on the current moment.