mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
add explain tests for openapiv3
This commit is contained in:
parent
8249a827bd
commit
9597abd089
@ -14,24 +14,31 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package explain
|
package explain_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
sptest "k8s.io/apimachinery/pkg/util/strategicpatch/testing"
|
sptest "k8s.io/apimachinery/pkg/util/strategicpatch/testing"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
|
"k8s.io/client-go/discovery"
|
||||||
|
openapiclient "k8s.io/client-go/openapi"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
clienttestutil "k8s.io/client-go/util/testing"
|
||||||
|
"k8s.io/kubectl/pkg/cmd/explain"
|
||||||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||||
"k8s.io/kubectl/pkg/util/openapi"
|
"k8s.io/kubectl/pkg/util/openapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
fakeSchema = sptest.Fake{Path: filepath.Join("..", "..", "..", "testdata", "openapi", "swagger.json")}
|
testDataPath = filepath.Join("..", "..", "..", "testdata")
|
||||||
|
fakeSchema = sptest.Fake{Path: filepath.Join(testDataPath, "openapi", "swagger.json")}
|
||||||
FakeOpenAPISchema = testOpenAPISchema{
|
FakeOpenAPISchema = testOpenAPISchema{
|
||||||
OpenAPISchemaFn: func() (openapi.Resources, error) {
|
OpenAPISchemaFn: func() (openapi.Resources, error) {
|
||||||
s, err := fakeSchema.OpenAPISchema()
|
s, err := fakeSchema.OpenAPISchema()
|
||||||
@ -51,8 +58,8 @@ func TestExplainInvalidArgs(t *testing.T) {
|
|||||||
tf := cmdtesting.NewTestFactory()
|
tf := cmdtesting.NewTestFactory()
|
||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
opts := NewExplainOptions("kubectl", genericclioptions.NewTestIOStreamsDiscard())
|
opts := explain.NewExplainOptions("kubectl", genericclioptions.NewTestIOStreamsDiscard())
|
||||||
cmd := NewCmdExplain("kubectl", tf, genericclioptions.NewTestIOStreamsDiscard())
|
cmd := explain.NewCmdExplain("kubectl", tf, genericclioptions.NewTestIOStreamsDiscard())
|
||||||
err := opts.Complete(tf, cmd, []string{})
|
err := opts.Complete(tf, cmd, []string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error %v", err)
|
t.Fatalf("unexpected error %v", err)
|
||||||
@ -78,8 +85,8 @@ func TestExplainNotExistResource(t *testing.T) {
|
|||||||
tf := cmdtesting.NewTestFactory()
|
tf := cmdtesting.NewTestFactory()
|
||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
opts := NewExplainOptions("kubectl", genericclioptions.NewTestIOStreamsDiscard())
|
opts := explain.NewExplainOptions("kubectl", genericclioptions.NewTestIOStreamsDiscard())
|
||||||
cmd := NewCmdExplain("kubectl", tf, genericclioptions.NewTestIOStreamsDiscard())
|
cmd := explain.NewCmdExplain("kubectl", tf, genericclioptions.NewTestIOStreamsDiscard())
|
||||||
err := opts.Complete(tf, cmd, []string{"foo"})
|
err := opts.Complete(tf, cmd, []string{"foo"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error %v", err)
|
t.Fatalf("unexpected error %v", err)
|
||||||
@ -96,30 +103,106 @@ func TestExplainNotExistResource(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExplainNotExistVersion(t *testing.T) {
|
type explainTestCase struct {
|
||||||
tf := cmdtesting.NewTestFactory()
|
Name string
|
||||||
defer tf.Cleanup()
|
Args []string
|
||||||
|
Flags map[string]string
|
||||||
|
ExpectPattern []string
|
||||||
|
ExpectErrorPattern string
|
||||||
|
|
||||||
opts := NewExplainOptions("kubectl", genericclioptions.NewTestIOStreamsDiscard())
|
// Custom OpenAPI V3 client to use for the test. If nil, a default one will
|
||||||
cmd := NewCmdExplain("kubectl", tf, genericclioptions.NewTestIOStreamsDiscard())
|
// be provided
|
||||||
err := opts.Complete(tf, cmd, []string{"pods"})
|
OpenAPIV3SchemaFn func() (openapiclient.Client, error)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %v", err)
|
|
||||||
}
|
|
||||||
opts.APIVersion = "v99"
|
|
||||||
|
|
||||||
err = opts.Validate()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = opts.Run()
|
|
||||||
if err.Error() != "couldn't find resource for \"/v99, Kind=Pod\"" {
|
|
||||||
t.Errorf("unexpected non-error")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExplain(t *testing.T) {
|
var explainV2Cases = []explainTestCase{
|
||||||
|
{
|
||||||
|
Name: "Basic",
|
||||||
|
Args: []string{"pods"},
|
||||||
|
ExpectPattern: []string{`\s*KIND:[\t ]*Pod\s*`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Recursive",
|
||||||
|
Args: []string{"pods"},
|
||||||
|
Flags: map[string]string{"recursive": "true"},
|
||||||
|
ExpectPattern: []string{`\s*KIND:[\t ]*Pod\s*`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "DefaultAPIVersion",
|
||||||
|
Args: []string{"horizontalpodautoscalers"},
|
||||||
|
Flags: map[string]string{"api-version": "autoscaling/v1"},
|
||||||
|
ExpectPattern: []string{`\s*VERSION:[\t ]*(v1|autoscaling/v1)\s*`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "NonExistingAPIVersion",
|
||||||
|
Args: []string{"pods"},
|
||||||
|
Flags: map[string]string{"api-version": "v99"},
|
||||||
|
ExpectErrorPattern: `couldn't find resource for \"/v99, (Kind=Pod|Resource=pods)\"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "NonExistingResource",
|
||||||
|
Args: []string{"foo"},
|
||||||
|
ExpectErrorPattern: `the server doesn't have a resource type "foo"`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExplainOpenAPIV2(t *testing.T) {
|
||||||
|
runExplainTestCases(t, explainV2Cases)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExplainOpenAPIV3(t *testing.T) {
|
||||||
|
|
||||||
|
fallbackV3SchemaFn := func() (openapiclient.Client, error) {
|
||||||
|
fakeDiscoveryClient := discovery.NewDiscoveryClientForConfigOrDie(&rest.Config{Host: "https://not.a.real.site:65543/"})
|
||||||
|
return fakeDiscoveryClient.OpenAPIV3(), nil
|
||||||
|
}
|
||||||
|
// Returns a client that causes fallback to v2 implementation
|
||||||
|
cases := []explainTestCase{
|
||||||
|
{
|
||||||
|
// No --output, but OpenAPIV3 enabled should fall back to v2 if
|
||||||
|
// v2 is not available. Shows this by making openapiv3 client
|
||||||
|
// point to a bad URL. So the fact the proper data renders is
|
||||||
|
// indication v2 was used instead.
|
||||||
|
Name: "Fallback",
|
||||||
|
Args: []string{"pods"},
|
||||||
|
ExpectPattern: []string{`\s*KIND:[\t ]*Pod\s*`},
|
||||||
|
OpenAPIV3SchemaFn: fallbackV3SchemaFn,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "NonDefaultAPIVersion",
|
||||||
|
Args: []string{"horizontalpodautoscalers"},
|
||||||
|
Flags: map[string]string{"api-version": "autoscaling/v2"},
|
||||||
|
ExpectPattern: []string{`\s*VERSION:[\t ]*(v2|autoscaling/v2)\s*`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Show that explicitly specifying --output plaintext-openapiv2 causes
|
||||||
|
// old implementation to be used even though OpenAPIV3 is enabled
|
||||||
|
Name: "OutputPlaintextV2",
|
||||||
|
Args: []string{"pods"},
|
||||||
|
Flags: map[string]string{"output": "plaintext-openapiv2"},
|
||||||
|
ExpectPattern: []string{`\s*KIND:[\t ]*Pod\s*`},
|
||||||
|
OpenAPIV3SchemaFn: fallbackV3SchemaFn,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cases = append(cases, explainV2Cases...)
|
||||||
|
|
||||||
|
cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.ExplainOpenapiV3}, t, func(t *testing.T) {
|
||||||
|
runExplainTestCases(t, cases)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func runExplainTestCases(t *testing.T, cases []explainTestCase) {
|
||||||
|
fakeServer, err := clienttestutil.NewFakeOpenAPIV3Server(filepath.Join(testDataPath, "openapi", "v3"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error starting fake openapi server: %v", err.Error())
|
||||||
|
}
|
||||||
|
defer fakeServer.HttpServer.Close()
|
||||||
|
|
||||||
|
openapiV3SchemaFn := func() (openapiclient.Client, error) {
|
||||||
|
fakeDiscoveryClient := discovery.NewDiscoveryClientForConfigOrDie(&rest.Config{Host: fakeServer.HttpServer.URL})
|
||||||
|
return fakeDiscoveryClient.OpenAPIV3(), nil
|
||||||
|
}
|
||||||
|
|
||||||
tf := cmdtesting.NewTestFactory()
|
tf := cmdtesting.NewTestFactory()
|
||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
@ -127,29 +210,72 @@ func TestExplain(t *testing.T) {
|
|||||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||||
|
|
||||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||||
cmd := NewCmdExplain("kubectl", tf, ioStreams)
|
|
||||||
cmd.Run(cmd, []string{"pods"})
|
|
||||||
if !strings.Contains(buf.String(), "KIND: Pod") {
|
|
||||||
t.Fatalf("expected output should include pod kind")
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Flags().Set("recursive", "true")
|
type catchFatal error
|
||||||
cmd.Run(cmd, []string{"pods"})
|
|
||||||
if !strings.Contains(buf.String(), "KIND: Pod") ||
|
|
||||||
!strings.Contains(buf.String(), "annotations\t<map[string]string>") {
|
|
||||||
t.Fatalf("expected output should include pod kind")
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Flags().Set("api-version", "batch/v1")
|
for _, tcase := range cases {
|
||||||
cmd.Run(cmd, []string{"cronjobs"})
|
|
||||||
if !strings.Contains(buf.String(), "VERSION: batch/v1") {
|
|
||||||
t.Fatalf("expected output should include pod batch/v1")
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Flags().Set("api-version", "batch/v1beta1")
|
t.Run(tcase.Name, func(t *testing.T) {
|
||||||
cmd.Run(cmd, []string{"cronjobs"})
|
|
||||||
if !strings.Contains(buf.String(), "VERSION: batch/v1beta1") {
|
// Catch os.Exit calls for tests which expect them
|
||||||
t.Fatalf("expected output should include pod batch/v1beta1")
|
// and replace them with panics that we catch in each test
|
||||||
|
// to check if it is expected.
|
||||||
|
cmdutil.BehaviorOnFatal(func(str string, code int) {
|
||||||
|
panic(catchFatal(errors.New(str)))
|
||||||
|
})
|
||||||
|
defer cmdutil.DefaultBehaviorOnFatal()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
// Catch panic and check at end of test if it is
|
||||||
|
// expected.
|
||||||
|
if panicErr := recover(); panicErr != nil {
|
||||||
|
if e := panicErr.(catchFatal); e != nil {
|
||||||
|
err = e
|
||||||
|
} else {
|
||||||
|
panic(panicErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if tcase.OpenAPIV3SchemaFn != nil {
|
||||||
|
tf.OpenAPIV3ClientFunc = tcase.OpenAPIV3SchemaFn
|
||||||
|
} else {
|
||||||
|
tf.OpenAPIV3ClientFunc = openapiV3SchemaFn
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := explain.NewCmdExplain("kubectl", tf, ioStreams)
|
||||||
|
for k, v := range tcase.Flags {
|
||||||
|
if err := cmd.Flags().Set(k, v); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.Run(cmd, tcase.Args)
|
||||||
|
}()
|
||||||
|
|
||||||
|
for _, rexp := range tcase.ExpectPattern {
|
||||||
|
if matched, err := regexp.MatchString(rexp, buf.String()); err != nil || !matched {
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else {
|
||||||
|
t.Errorf("expected output to match regex:\n\t%s\ninstead got:\n\t%s", rexp, buf.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if matched, regexErr := regexp.MatchString(tcase.ExpectErrorPattern, err.Error()); len(tcase.ExpectErrorPattern) == 0 || regexErr != nil || !matched {
|
||||||
|
t.Fatalf("unexpected error: %s did not match regex %s (%v)", err.Error(),
|
||||||
|
tcase.ExpectErrorPattern, regexErr)
|
||||||
|
}
|
||||||
|
} else if len(tcase.ExpectErrorPattern) > 0 {
|
||||||
|
t.Fatalf("did not trigger expected error: %s in output:\n%s", tcase.ExpectErrorPattern, buf.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,11 +287,11 @@ func TestAlphaEnablement(t *testing.T) {
|
|||||||
f := cmdtesting.NewTestFactory()
|
f := cmdtesting.NewTestFactory()
|
||||||
defer f.Cleanup()
|
defer f.Cleanup()
|
||||||
|
|
||||||
cmd := NewCmdExplain("kubectl", f, genericclioptions.NewTestIOStreamsDiscard())
|
cmd := explain.NewCmdExplain("kubectl", f, genericclioptions.NewTestIOStreamsDiscard())
|
||||||
require.Nil(t, cmd.Flags().Lookup(flag), "flag %q should not be registered without the %q feature enabled", flag, feature)
|
require.Nil(t, cmd.Flags().Lookup(flag), "flag %q should not be registered without the %q feature enabled", flag, feature)
|
||||||
|
|
||||||
cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{feature}, t, func(t *testing.T) {
|
cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{feature}, t, func(t *testing.T) {
|
||||||
cmd := NewCmdExplain("kubectl", f, genericclioptions.NewTestIOStreamsDiscard())
|
cmd := explain.NewCmdExplain("kubectl", f, genericclioptions.NewTestIOStreamsDiscard())
|
||||||
require.NotNil(t, cmd.Flags().Lookup(flag), "flag %q should be registered with the %q feature enabled", flag, feature)
|
require.NotNil(t, cmd.Flags().Lookup(flag), "flag %q should be registered with the %q feature enabled", flag, feature)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
28772
staging/src/k8s.io/kubectl/testdata/openapi/v3/api/v1.json
vendored
Normal file
28772
staging/src/k8s.io/kubectl/testdata/openapi/v3/api/v1.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
staging/src/k8s.io/kubectl/testdata/openapi/v3/apis/autoscaling/v1.json
vendored
Normal file
1
staging/src/k8s.io/kubectl/testdata/openapi/v3/apis/autoscaling/v1.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
staging/src/k8s.io/kubectl/testdata/openapi/v3/apis/autoscaling/v2.json
vendored
Normal file
1
staging/src/k8s.io/kubectl/testdata/openapi/v3/apis/autoscaling/v2.json
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user