Set up examples gate job for 1.11 (#9345)
There were things changed upstream which have blocked the examples test from running correctly: - PodSecurityPolicy has been MOVED from extensions API group into policy. All samples that are using this resource type needs a change. - The 'GetCodecForObject()' function in testapi/testapi package was removed in 1.11 for unknown reasons. This change has broken our job as well as the test code in the kubernetes/examples project. - Kubernetes is now using 1.10.2+ verion of Go for compilation so we have to change the .travis.yml accordingly. This PR fixes the above problems. Note also, the `TestReadme` function is **removed** for two reasons: - It is only testing the snippets in the `volumes.md` file which is now broken because of the introduction of service account token injection. - It is regarded as too aggressive/restrictive a test. We cannot guarantee that YAML snippets in the markdown files are complete and can be used as is. Instead I believe we should encourage people to use template snippets that contain the **key** information for a topic. When we see a need for a full template that can be used directly, we may want to extract the YAML contents into the `examples` subdirectory.
This commit is contained in:
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.9.3
|
- 1.10.2
|
||||||
|
|
||||||
# Don't want default ./... here:
|
# Don't want default ./... here:
|
||||||
install:
|
install:
|
||||||
@@ -10,7 +10,7 @@ install:
|
|||||||
# Fetch dependencies for us to run the tests in test/examples_test.go
|
# Fetch dependencies for us to run the tests in test/examples_test.go
|
||||||
- go get -t -v k8s.io/website/test
|
- go get -t -v k8s.io/website/test
|
||||||
# Make sure we are testing against the correct branch
|
# Make sure we are testing against the correct branch
|
||||||
- pushd $GOPATH/src/k8s.io/kubernetes && git checkout release-1.10 && popd
|
- pushd $GOPATH/src/k8s.io/kubernetes && git checkout release-1.11 && popd
|
||||||
|
|
||||||
# Simplified deduplication of dependencies.
|
# Simplified deduplication of dependencies.
|
||||||
- cp -L -R $GOPATH/src/k8s.io/kubernetes/vendor/ $GOPATH/src/
|
- cp -L -R $GOPATH/src/k8s.io/kubernetes/vendor/ $GOPATH/src/
|
||||||
|
|||||||
+35
-97
@@ -24,7 +24,6 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -33,6 +32,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
"k8s.io/apimachinery/pkg/util/yaml"
|
"k8s.io/apimachinery/pkg/util/yaml"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
"k8s.io/kubernetes/pkg/api/testapi"
|
"k8s.io/kubernetes/pkg/api/testapi"
|
||||||
"k8s.io/kubernetes/pkg/apis/admissionregistration"
|
"k8s.io/kubernetes/pkg/apis/admissionregistration"
|
||||||
ar_validation "k8s.io/kubernetes/pkg/apis/admissionregistration/validation"
|
ar_validation "k8s.io/kubernetes/pkg/apis/admissionregistration/validation"
|
||||||
@@ -59,6 +59,33 @@ import (
|
|||||||
schedulerapilatest "k8s.io/kubernetes/pkg/scheduler/api/latest"
|
schedulerapilatest "k8s.io/kubernetes/pkg/scheduler/api/latest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func getCodecForObject(obj runtime.Object) (runtime.Codec, error) {
|
||||||
|
kinds, _, err := legacyscheme.Scheme.ObjectKinds(obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unexpected encoding error: %v", err)
|
||||||
|
}
|
||||||
|
kind := kinds[0]
|
||||||
|
|
||||||
|
for _, group := range testapi.Groups {
|
||||||
|
if group.GroupVersion().Group != kind.Group {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if legacyscheme.Scheme.Recognizes(kind) {
|
||||||
|
return group.Codec(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Codec used for unversioned types
|
||||||
|
if legacyscheme.Scheme.Recognizes(kind) {
|
||||||
|
serializer, ok := runtime.SerializerInfoForMediaType(legacyscheme.Codecs.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("no serializer registered for json")
|
||||||
|
}
|
||||||
|
return serializer.Serializer, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unexpected kind: %v", kind)
|
||||||
|
}
|
||||||
|
|
||||||
func validateObject(obj runtime.Object) (errors field.ErrorList) {
|
func validateObject(obj runtime.Object) (errors field.ErrorList) {
|
||||||
// Enable CustomPodDNS for testing
|
// Enable CustomPodDNS for testing
|
||||||
utilfeature.DefaultFeatureGate.Set("CustomPodDNS=true")
|
utilfeature.DefaultFeatureGate.Set("CustomPodDNS=true")
|
||||||
@@ -173,8 +200,8 @@ func validateObject(obj runtime.Object) (errors field.ErrorList) {
|
|||||||
t.Namespace = api.NamespaceDefault
|
t.Namespace = api.NamespaceDefault
|
||||||
}
|
}
|
||||||
errors = ext_validation.ValidateIngress(t)
|
errors = ext_validation.ValidateIngress(t)
|
||||||
case *extensions.PodSecurityPolicy:
|
case *policy.PodSecurityPolicy:
|
||||||
errors = ext_validation.ValidatePodSecurityPolicy(t)
|
errors = policy_validation.ValidatePodSecurityPolicy(t)
|
||||||
case *extensions.ReplicaSet:
|
case *extensions.ReplicaSet:
|
||||||
if t.Namespace == "" {
|
if t.Namespace == "" {
|
||||||
t.Namespace = api.NamespaceDefault
|
t.Namespace = api.NamespaceDefault
|
||||||
@@ -289,9 +316,9 @@ func TestExampleObjectSchemas(t *testing.T) {
|
|||||||
"nginx-deployment": {&extensions.Deployment{}},
|
"nginx-deployment": {&extensions.Deployment{}},
|
||||||
},
|
},
|
||||||
"docs/concepts/policy": {
|
"docs/concepts/policy": {
|
||||||
"privileged-psp": {&extensions.PodSecurityPolicy{}},
|
"privileged-psp": {&policy.PodSecurityPolicy{}},
|
||||||
"restricted-psp": {&extensions.PodSecurityPolicy{}},
|
"restricted-psp": {&policy.PodSecurityPolicy{}},
|
||||||
"example-psp": {&extensions.PodSecurityPolicy{}},
|
"example-psp": {&policy.PodSecurityPolicy{}},
|
||||||
},
|
},
|
||||||
"docs/concepts/services-networking": {
|
"docs/concepts/services-networking": {
|
||||||
"curlpod": {&extensions.Deployment{}},
|
"curlpod": {&extensions.Deployment{}},
|
||||||
@@ -308,8 +335,8 @@ func TestExampleObjectSchemas(t *testing.T) {
|
|||||||
"frontend": {&extensions.ReplicaSet{}},
|
"frontend": {&extensions.ReplicaSet{}},
|
||||||
"hpa-rs": {&autoscaling.HorizontalPodAutoscaler{}},
|
"hpa-rs": {&autoscaling.HorizontalPodAutoscaler{}},
|
||||||
"job": {&batch.Job{}},
|
"job": {&batch.Job{}},
|
||||||
"my-repset": {&extensions.ReplicaSet{}},
|
|
||||||
"nginx-deployment": {&extensions.Deployment{}},
|
"nginx-deployment": {&extensions.Deployment{}},
|
||||||
|
"my-repset": {&extensions.ReplicaSet{}},
|
||||||
"replication": {&api.ReplicationController{}},
|
"replication": {&api.ReplicationController{}},
|
||||||
},
|
},
|
||||||
"docs/tasks/access-application-cluster": {
|
"docs/tasks/access-application-cluster": {
|
||||||
@@ -540,7 +567,7 @@ func TestExampleObjectSchemas(t *testing.T) {
|
|||||||
// &schedulerapi.Policy, and remove this
|
// &schedulerapi.Policy, and remove this
|
||||||
// special case
|
// special case
|
||||||
} else {
|
} else {
|
||||||
codec, err := testapi.GetCodecForObject(expectedType)
|
codec, err := getCodecForObject(expectedType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Could not get codec for %s: %s", expectedType, err)
|
t.Errorf("Could not get codec for %s: %s", expectedType, err)
|
||||||
}
|
}
|
||||||
@@ -562,92 +589,3 @@ func TestExampleObjectSchemas(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This regex is tricky, but it works. For future me, here is the decode:
|
|
||||||
//
|
|
||||||
// Flags: (?ms) = multiline match, allow . to match \n
|
|
||||||
// 1) Look for a line that starts with ``` (a markdown code block)
|
|
||||||
// 2) (?: ... ) = non-capturing group
|
|
||||||
// 3) (P<name>) = capture group as "name"
|
|
||||||
// 4) Look for #1 followed by either:
|
|
||||||
// 4a) "yaml" followed by any word-characters followed by a newline (e.g. ```yamlfoo\n)
|
|
||||||
// 4b) "any word-characters followed by a newline (e.g. ```json\n)
|
|
||||||
// 5) Look for either:
|
|
||||||
// 5a) #4a followed by one or more characters (non-greedy)
|
|
||||||
// 5b) #4b followed by { followed by one or more characters (non-greedy) followed by }
|
|
||||||
// 6) Look for #5 followed by a newline followed by ``` (end of the code block)
|
|
||||||
//
|
|
||||||
// This could probably be simplified, but is already too delicate. Before any
|
|
||||||
// real changes, we should have a test case that just tests this regex.
|
|
||||||
var sampleRegexp = regexp.MustCompile("(?ms)^```(?:(?P<type>yaml)\\w*\\n(?P<content>.+?)|\\w*\\n(?P<content>\\{.+?\\}))\\n^```")
|
|
||||||
var subsetRegexp = regexp.MustCompile("(?ms)\\.{3}")
|
|
||||||
|
|
||||||
// Validates examples embedded in Markdown files.
|
|
||||||
func TestReadme(t *testing.T) {
|
|
||||||
// BlockVolume required for local volume example
|
|
||||||
utilfeature.DefaultFeatureGate.Set("BlockVolume=true")
|
|
||||||
|
|
||||||
paths := []struct {
|
|
||||||
file string
|
|
||||||
expectedType []runtime.Object // List of all valid types for the whole doc
|
|
||||||
}{
|
|
||||||
{"../content/en/docs/concepts/storage/volumes.md", []runtime.Object{
|
|
||||||
&api.Pod{},
|
|
||||||
&api.PersistentVolume{},
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, path := range paths {
|
|
||||||
data, err := ioutil.ReadFile(path.file)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unable to read file %s: %v", path, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
matches := sampleRegexp.FindAllStringSubmatch(string(data), -1)
|
|
||||||
if matches == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, match := range matches {
|
|
||||||
var content, subtype string
|
|
||||||
for i, name := range sampleRegexp.SubexpNames() {
|
|
||||||
if name == "type" {
|
|
||||||
subtype = match[i]
|
|
||||||
}
|
|
||||||
if name == "content" && match[i] != "" {
|
|
||||||
content = match[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if subtype == "yaml" && subsetRegexp.FindString(content) != "" {
|
|
||||||
t.Logf("skipping (%s): \n%s", subtype, content)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
json, err := yaml.ToJSON([]byte(content))
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s could not be converted to JSON: %v\n%s", path, err, string(content))
|
|
||||||
}
|
|
||||||
|
|
||||||
var expectedType runtime.Object
|
|
||||||
for _, expectedType = range path.expectedType {
|
|
||||||
err = runtime.DecodeInto(testapi.Default.Codec(), json, expectedType)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s did not decode correctly: %v\n%s", path, err, string(content))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors := validateObject(expectedType); len(errors) > 0 {
|
|
||||||
t.Errorf("%s did not validate correctly: %v", path, errors)
|
|
||||||
}
|
|
||||||
_, err = runtime.Encode(testapi.Default.Codec(), expectedType)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Could not encode object: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user