Move pkg/kubectl/polymorphichelpers staging

This commit is contained in:
Sean Sullivan
2019-07-27 19:48:15 -07:00
parent 4f15c017ea
commit 9f3384f02f
67 changed files with 39 additions and 35 deletions

View File

@@ -85,7 +85,6 @@ filegroup(
"//pkg/kubectl/cmd:all-srcs",
"//pkg/kubectl/explain:all-srcs",
"//pkg/kubectl/generate:all-srcs",
"//pkg/kubectl/polymorphichelpers:all-srcs",
"//pkg/kubectl/proxy:all-srcs",
],
tags = ["automanaged"],

View File

@@ -6,7 +6,6 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/annotate",
visibility = ["//visibility:public"],
deps = [
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
@@ -16,6 +15,7 @@ go_library(
"//staging/src/k8s.io/cli-runtime/pkg/printers:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/resource:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/i18n:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/templates:go_default_library",

View File

@@ -35,10 +35,10 @@ import (
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
)
// AnnotateOptions have the data required to perform the annotate operation

View File

@@ -7,7 +7,6 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//pkg/kubectl/cmd/exec:go_default_library",
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
@@ -15,6 +14,7 @@ go_library(
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/tools/remotecommand:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/i18n:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/templates:go_default_library",
@@ -30,7 +30,6 @@ go_test(
deps = [
"//pkg/kubectl/cmd/exec:go_default_library",
"//pkg/kubectl/cmd/testing:go_default_library",
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
@@ -39,6 +38,7 @@ go_test(
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/rest/fake:go_default_library",
"//staging/src/k8s.io/client-go/tools/remotecommand:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
],
)

View File

@@ -32,11 +32,11 @@ import (
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubernetes/pkg/kubectl/cmd/exec"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
)
var (

View File

@@ -33,10 +33,10 @@ import (
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubernetes/pkg/kubectl/cmd/exec"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
)
type fakeRemoteAttach struct {

View File

@@ -9,7 +9,6 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/clusterinfo",
visibility = ["//visibility:public"],
deps = [
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library",
@@ -20,6 +19,7 @@ go_library(
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/i18n:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/templates:go_default_library",

View File

@@ -32,10 +32,10 @@ import (
appsv1client "k8s.io/client-go/kubernetes/typed/apps/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
)
const (

View File

@@ -6,7 +6,6 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/exec",
visibility = ["//visibility:public"],
deps = [
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
@@ -15,6 +14,7 @@ go_library(
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/tools/remotecommand:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/i18n:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/interrupt:go_default_library",

View File

@@ -33,12 +33,12 @@ import (
"k8s.io/client-go/tools/remotecommand"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/interrupt"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubectl/pkg/util/term"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
)
var (

View File

@@ -8,7 +8,6 @@ go_library(
deps = [
"//pkg/kubectl/generate:go_default_library",
"//pkg/kubectl/generate/versioned:go_default_library",
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
@@ -20,6 +19,7 @@ go_library(
"//staging/src/k8s.io/cli-runtime/pkg/resource:go_default_library",
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/i18n:go_default_library",

View File

@@ -34,13 +34,13 @@ import (
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/dynamic"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubernetes/pkg/kubectl/generate"
generateversioned "k8s.io/kubernetes/pkg/kubectl/generate/versioned"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
)
var (

View File

@@ -6,13 +6,13 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/logs",
visibility = ["//visibility:public"],
deps = [
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/i18n:go_default_library",

View File

@@ -33,11 +33,11 @@ import (
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
)
const (

View File

@@ -6,7 +6,6 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/portforward",
visibility = ["//visibility:public"],
deps = [
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
@@ -16,6 +15,7 @@ go_library(
"//staging/src/k8s.io/client-go/tools/portforward:go_default_library",
"//staging/src/k8s.io/client-go/transport/spdy:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/i18n:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/templates:go_default_library",

View File

@@ -37,10 +37,10 @@ import (
"k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
)
// PortForwardOptions contains all the options for running the port-forward cli command.

View File

@@ -21,7 +21,6 @@ go_library(
],
deps = [
"//pkg/kubectl/cmd/set:go_default_library",
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
@@ -38,6 +37,7 @@ go_library(
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
"//staging/src/k8s.io/client-go/tools/watch:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/i18n:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/interrupt:go_default_library",

View File

@@ -25,10 +25,10 @@ import (
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
)
var (

View File

@@ -27,11 +27,11 @@ import (
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubernetes/pkg/kubectl/cmd/set"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
)
// PauseOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of

View File

@@ -26,11 +26,11 @@ import (
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubernetes/pkg/kubectl/cmd/set"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
)
// RestartOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of

View File

@@ -27,11 +27,11 @@ import (
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubernetes/pkg/kubectl/cmd/set"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
)
// ResumeOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of

View File

@@ -36,11 +36,11 @@ import (
"k8s.io/client-go/tools/cache"
watchtools "k8s.io/client-go/tools/watch"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/interrupt"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
)
var (

View File

@@ -20,7 +20,7 @@ import (
"fmt"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/printers"

View File

@@ -13,7 +13,6 @@ go_library(
"//pkg/kubectl/cmd/logs:go_default_library",
"//pkg/kubectl/generate:go_default_library",
"//pkg/kubectl/generate/versioned:go_default_library",
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
@@ -30,6 +29,7 @@ go_library(
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
"//staging/src/k8s.io/client-go/tools/watch:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/i18n:go_default_library",

View File

@@ -41,6 +41,7 @@ import (
"k8s.io/client-go/tools/cache"
watchtools "k8s.io/client-go/tools/watch"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/i18n"
@@ -53,7 +54,6 @@ import (
"k8s.io/kubernetes/pkg/kubectl/cmd/logs"
"k8s.io/kubernetes/pkg/kubectl/generate"
generateversioned "k8s.io/kubernetes/pkg/kubectl/generate/versioned"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
uexec "k8s.io/utils/exec"
)

View File

@@ -21,7 +21,6 @@ go_library(
deps = [
"//pkg/kubectl/cmd/set/env:go_default_library",
"//pkg/kubectl/generate/versioned:go_default_library",
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/api/rbac/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
@@ -37,6 +36,7 @@ go_library(
"//staging/src/k8s.io/cli-runtime/pkg/resource:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/polymorphichelpers:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/i18n:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/templates:go_default_library",

View File

@@ -35,10 +35,10 @@ import (
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/kubernetes"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/templates"
envutil "k8s.io/kubernetes/pkg/kubectl/cmd/set/env"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
)
var (

View File

@@ -30,10 +30,10 @@ import (
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
)
// ImageOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of

View File

@@ -30,11 +30,11 @@ import (
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
generateversioned "k8s.io/kubernetes/pkg/kubectl/generate/versioned"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
)
var (

View File

@@ -31,10 +31,10 @@ import (
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
)
var (

View File

@@ -1,115 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"attachablepodforobject.go",
"canbeexposed.go",
"helpers.go",
"history.go",
"historyviewer.go",
"interface.go",
"logsforobject.go",
"mapbasedselectorforobject.go",
"objectpauser.go",
"objectrestarter.go",
"objectresumer.go",
"portsforobject.go",
"protocolsforobject.go",
"rollback.go",
"rollbacker.go",
"rollout_status.go",
"statusviewer.go",
"updatepodspec.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/polymorphichelpers",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/api/apps/v1:go_default_library",
"//staging/src/k8s.io/api/apps/v1beta1:go_default_library",
"//staging/src/k8s.io/api/apps/v1beta2:go_default_library",
"//staging/src/k8s.io/api/batch/v1:go_default_library",
"//staging/src/k8s.io/api/batch/v1beta1:go_default_library",
"//staging/src/k8s.io/api/batch/v2alpha1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/api/extensions/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/apps/v1:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/tools/watch:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/apps:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/describe/versioned:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/deployment:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/podutils:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/slice:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"canbeexposed_test.go",
"helpers_test.go",
"history_test.go",
"logsforobject_test.go",
"mapbasedselectorforobject_test.go",
"objectpauser_test.go",
"objectresumer_test.go",
"portsforobject_test.go",
"protocolsforobject_test.go",
"rollback_test.go",
"rollout_status_test.go",
"updatepodspec_test.go",
],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/api/apps/v1:go_default_library",
"//staging/src/k8s.io/api/apps/v1beta1:go_default_library",
"//staging/src/k8s.io/api/apps/v1beta2:go_default_library",
"//staging/src/k8s.io/api/batch/v1:go_default_library",
"//staging/src/k8s.io/api/batch/v1beta1:go_default_library",
"//staging/src/k8s.io/api/batch/v2alpha1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/api/extensions/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
"//staging/src/k8s.io/client-go/testing:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/podutils:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -1,54 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"fmt"
"sort"
"time"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/kubectl/pkg/util/podutils"
)
// attachablePodForObject returns the pod to which to attach given an object.
func attachablePodForObject(restClientGetter genericclioptions.RESTClientGetter, object runtime.Object, timeout time.Duration) (*corev1.Pod, error) {
switch t := object.(type) {
case *corev1.Pod:
return t, nil
}
clientConfig, err := restClientGetter.ToRESTConfig()
if err != nil {
return nil, err
}
clientset, err := corev1client.NewForConfig(clientConfig)
if err != nil {
return nil, err
}
namespace, selector, err := SelectorsForObject(object)
if err != nil {
return nil, fmt.Errorf("cannot attach to %T: %v", object, err)
}
sortBy := func(pods []*corev1.Pod) sort.Interface { return sort.Reverse(podutils.ActivePods(pods)) }
pod, _, err := GetFirstPod(clientset, namespace, selector.String(), timeout, sortBy)
return pod, err
}

View File

@@ -1,44 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"fmt"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// Check whether the kind of resources could be exposed
func canBeExposed(kind schema.GroupKind) error {
switch kind {
case
corev1.SchemeGroupVersion.WithKind("ReplicationController").GroupKind(),
corev1.SchemeGroupVersion.WithKind("Service").GroupKind(),
corev1.SchemeGroupVersion.WithKind("Pod").GroupKind(),
appsv1.SchemeGroupVersion.WithKind("Deployment").GroupKind(),
appsv1.SchemeGroupVersion.WithKind("ReplicaSet").GroupKind(),
extensionsv1beta1.SchemeGroupVersion.WithKind("Deployment").GroupKind(),
extensionsv1beta1.SchemeGroupVersion.WithKind("ReplicaSet").GroupKind():
// nothing to do here
default:
return fmt.Errorf("cannot expose a %s", kind)
}
return nil
}

View File

@@ -1,68 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"testing"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestCanBeExposed(t *testing.T) {
tests := []struct {
kind schema.GroupKind
expectErr bool
}{
{
kind: corev1.SchemeGroupVersion.WithKind("ReplicationController").GroupKind(),
expectErr: false,
},
{
kind: corev1.SchemeGroupVersion.WithKind("Service").GroupKind(),
expectErr: false,
},
{
kind: corev1.SchemeGroupVersion.WithKind("Pod").GroupKind(),
expectErr: false,
},
{
kind: appsv1.SchemeGroupVersion.WithKind("Deployment").GroupKind(),
expectErr: false,
},
{
kind: extensionsv1beta1.SchemeGroupVersion.WithKind("ReplicaSet").GroupKind(),
expectErr: false,
},
{
kind: corev1.SchemeGroupVersion.WithKind("Node").GroupKind(),
expectErr: true,
},
}
for _, test := range tests {
err := canBeExposed(test.kind)
if test.expectErr && err == nil {
t.Error("unexpected non-error")
}
if !test.expectErr && err != nil {
t.Errorf("unexpected error: %v", err)
}
}
}

View File

@@ -1,191 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"context"
"fmt"
"sort"
"time"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
coreclient "k8s.io/client-go/kubernetes/typed/core/v1"
watchtools "k8s.io/client-go/tools/watch"
)
// GetFirstPod returns a pod matching the namespace and label selector
// and the number of all pods that match the label selector.
func GetFirstPod(client coreclient.PodsGetter, namespace string, selector string, timeout time.Duration, sortBy func([]*corev1.Pod) sort.Interface) (*corev1.Pod, int, error) {
options := metav1.ListOptions{LabelSelector: selector}
podList, err := client.Pods(namespace).List(options)
if err != nil {
return nil, 0, err
}
pods := []*corev1.Pod{}
for i := range podList.Items {
pod := podList.Items[i]
pods = append(pods, &pod)
}
if len(pods) > 0 {
sort.Sort(sortBy(pods))
return pods[0], len(podList.Items), nil
}
// Watch until we observe a pod
options.ResourceVersion = podList.ResourceVersion
w, err := client.Pods(namespace).Watch(options)
if err != nil {
return nil, 0, err
}
defer w.Stop()
condition := func(event watch.Event) (bool, error) {
return event.Type == watch.Added || event.Type == watch.Modified, nil
}
ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout)
defer cancel()
event, err := watchtools.UntilWithoutRetry(ctx, w, condition)
if err != nil {
return nil, 0, err
}
pod, ok := event.Object.(*corev1.Pod)
if !ok {
return nil, 0, fmt.Errorf("%#v is not a pod event", event)
}
return pod, 1, nil
}
// SelectorsForObject returns the pod label selector for a given object
func SelectorsForObject(object runtime.Object) (namespace string, selector labels.Selector, err error) {
switch t := object.(type) {
case *extensionsv1beta1.ReplicaSet:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return "", nil, fmt.Errorf("invalid label selector: %v", err)
}
case *appsv1.ReplicaSet:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return "", nil, fmt.Errorf("invalid label selector: %v", err)
}
case *appsv1beta2.ReplicaSet:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return "", nil, fmt.Errorf("invalid label selector: %v", err)
}
case *corev1.ReplicationController:
namespace = t.Namespace
selector = labels.SelectorFromSet(t.Spec.Selector)
case *appsv1.StatefulSet:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return "", nil, fmt.Errorf("invalid label selector: %v", err)
}
case *appsv1beta1.StatefulSet:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return "", nil, fmt.Errorf("invalid label selector: %v", err)
}
case *appsv1beta2.StatefulSet:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return "", nil, fmt.Errorf("invalid label selector: %v", err)
}
case *extensionsv1beta1.DaemonSet:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return "", nil, fmt.Errorf("invalid label selector: %v", err)
}
case *appsv1.DaemonSet:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return "", nil, fmt.Errorf("invalid label selector: %v", err)
}
case *appsv1beta2.DaemonSet:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return "", nil, fmt.Errorf("invalid label selector: %v", err)
}
case *extensionsv1beta1.Deployment:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return "", nil, fmt.Errorf("invalid label selector: %v", err)
}
case *appsv1.Deployment:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return "", nil, fmt.Errorf("invalid label selector: %v", err)
}
case *appsv1beta1.Deployment:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return "", nil, fmt.Errorf("invalid label selector: %v", err)
}
case *appsv1beta2.Deployment:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return "", nil, fmt.Errorf("invalid label selector: %v", err)
}
case *batchv1.Job:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return "", nil, fmt.Errorf("invalid label selector: %v", err)
}
case *corev1.Service:
namespace = t.Namespace
if t.Spec.Selector == nil || len(t.Spec.Selector) == 0 {
return "", nil, fmt.Errorf("invalid service '%s': Service is defined without a selector", t.Name)
}
selector = labels.SelectorFromSet(t.Spec.Selector)
default:
return "", nil, fmt.Errorf("selector for %T not implemented", object)
}
return namespace, selector, nil
}

View File

@@ -1,227 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"fmt"
"sort"
"testing"
"time"
corev1 "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/watch"
fakeexternal "k8s.io/client-go/kubernetes/fake"
testcore "k8s.io/client-go/testing"
"k8s.io/kubectl/pkg/util/podutils"
)
func TestGetFirstPod(t *testing.T) {
labelSet := map[string]string{"test": "selector"}
tests := []struct {
name string
podList *corev1.PodList
watching []watch.Event
sortBy func([]*corev1.Pod) sort.Interface
expected *corev1.Pod
expectedNum int
expectedErr bool
}{
{
name: "kubectl logs - two ready pods",
podList: newPodList(2, -1, -1, labelSet),
sortBy: func(pods []*corev1.Pod) sort.Interface { return podutils.ByLogging(pods) },
expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-1",
Namespace: metav1.NamespaceDefault,
CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC),
Labels: map[string]string{"test": "selector"},
},
Status: corev1.PodStatus{
Conditions: []corev1.PodCondition{
{
Status: corev1.ConditionTrue,
Type: corev1.PodReady,
},
},
},
},
expectedNum: 2,
},
{
name: "kubectl logs - one unhealthy, one healthy",
podList: newPodList(2, -1, 1, labelSet),
sortBy: func(pods []*corev1.Pod) sort.Interface { return podutils.ByLogging(pods) },
expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-2",
Namespace: metav1.NamespaceDefault,
CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 1, 0, time.UTC),
Labels: map[string]string{"test": "selector"},
},
Status: corev1.PodStatus{
Conditions: []corev1.PodCondition{
{
Status: corev1.ConditionTrue,
Type: corev1.PodReady,
},
},
ContainerStatuses: []corev1.ContainerStatus{{RestartCount: 5}},
},
},
expectedNum: 2,
},
{
name: "kubectl attach - two ready pods",
podList: newPodList(2, -1, -1, labelSet),
sortBy: func(pods []*corev1.Pod) sort.Interface { return sort.Reverse(podutils.ActivePods(pods)) },
expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-1",
Namespace: metav1.NamespaceDefault,
CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC),
Labels: map[string]string{"test": "selector"},
},
Status: corev1.PodStatus{
Conditions: []corev1.PodCondition{
{
Status: corev1.ConditionTrue,
Type: corev1.PodReady,
},
},
},
},
expectedNum: 2,
},
{
name: "kubectl attach - wait for ready pod",
podList: newPodList(1, 1, -1, labelSet),
watching: []watch.Event{
{
Type: watch.Modified,
Object: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-1",
Namespace: metav1.NamespaceDefault,
CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC),
Labels: map[string]string{"test": "selector"},
},
Status: corev1.PodStatus{
Conditions: []corev1.PodCondition{
{
Status: corev1.ConditionTrue,
Type: corev1.PodReady,
},
},
},
},
},
},
sortBy: func(pods []*corev1.Pod) sort.Interface { return sort.Reverse(podutils.ActivePods(pods)) },
expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-1",
Namespace: metav1.NamespaceDefault,
CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC),
Labels: map[string]string{"test": "selector"},
},
Status: corev1.PodStatus{
Conditions: []corev1.PodCondition{
{
Status: corev1.ConditionTrue,
Type: corev1.PodReady,
},
},
},
},
expectedNum: 1,
},
}
for i := range tests {
test := tests[i]
fake := fakeexternal.NewSimpleClientset(test.podList)
if len(test.watching) > 0 {
watcher := watch.NewFake()
for _, event := range test.watching {
switch event.Type {
case watch.Added:
go watcher.Add(event.Object)
case watch.Modified:
go watcher.Modify(event.Object)
}
}
fake.PrependWatchReactor("pods", testcore.DefaultWatchReactor(watcher, nil))
}
selector := labels.Set(labelSet).AsSelector()
pod, numPods, err := GetFirstPod(fake.CoreV1(), metav1.NamespaceDefault, selector.String(), 1*time.Minute, test.sortBy)
pod.Spec.SecurityContext = nil
if !test.expectedErr && err != nil {
t.Errorf("%s: unexpected error: %v", test.name, err)
continue
}
if test.expectedErr && err == nil {
t.Errorf("%s: expected an error", test.name)
continue
}
if test.expectedNum != numPods {
t.Errorf("%s: expected %d pods, got %d", test.name, test.expectedNum, numPods)
continue
}
if !apiequality.Semantic.DeepEqual(test.expected, pod) {
t.Errorf("%s:\nexpected pod:\n%#v\ngot:\n%#v\n\n", test.name, test.expected, pod)
}
}
}
func newPodList(count, isUnready, isUnhealthy int, labels map[string]string) *corev1.PodList {
pods := []corev1.Pod{}
for i := 0; i < count; i++ {
newPod := corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("pod-%d", i+1),
Namespace: metav1.NamespaceDefault,
CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, i, 0, time.UTC),
Labels: labels,
},
Status: corev1.PodStatus{
Conditions: []corev1.PodCondition{
{
Status: corev1.ConditionTrue,
Type: corev1.PodReady,
},
},
},
}
pods = append(pods, newPod)
}
if isUnready > -1 && isUnready < count {
pods[isUnready].Status.Conditions[0].Status = corev1.ConditionFalse
}
if isUnhealthy > -1 && isUnhealthy < count {
pods[isUnhealthy].Status.ContainerStatuses = []corev1.ContainerStatus{{RestartCount: 5}}
}
return &corev1.PodList{
Items: pods,
}
}

View File

@@ -1,390 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"bytes"
"fmt"
"io"
"text/tabwriter"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/client-go/kubernetes"
clientappsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
"k8s.io/kubectl/pkg/apps"
describe "k8s.io/kubectl/pkg/describe/versioned"
deploymentutil "k8s.io/kubectl/pkg/util/deployment"
sliceutil "k8s.io/kubectl/pkg/util/slice"
)
const (
ChangeCauseAnnotation = "kubernetes.io/change-cause"
)
// HistoryViewer provides an interface for resources have historical information.
type HistoryViewer interface {
ViewHistory(namespace, name string, revision int64) (string, error)
}
type HistoryVisitor struct {
clientset kubernetes.Interface
result HistoryViewer
}
func (v *HistoryVisitor) VisitDeployment(elem apps.GroupKindElement) {
v.result = &DeploymentHistoryViewer{v.clientset}
}
func (v *HistoryVisitor) VisitStatefulSet(kind apps.GroupKindElement) {
v.result = &StatefulSetHistoryViewer{v.clientset}
}
func (v *HistoryVisitor) VisitDaemonSet(kind apps.GroupKindElement) {
v.result = &DaemonSetHistoryViewer{v.clientset}
}
func (v *HistoryVisitor) VisitJob(kind apps.GroupKindElement) {}
func (v *HistoryVisitor) VisitPod(kind apps.GroupKindElement) {}
func (v *HistoryVisitor) VisitReplicaSet(kind apps.GroupKindElement) {}
func (v *HistoryVisitor) VisitReplicationController(kind apps.GroupKindElement) {}
func (v *HistoryVisitor) VisitCronJob(kind apps.GroupKindElement) {}
// HistoryViewerFor returns an implementation of HistoryViewer interface for the given schema kind
func HistoryViewerFor(kind schema.GroupKind, c kubernetes.Interface) (HistoryViewer, error) {
elem := apps.GroupKindElement(kind)
visitor := &HistoryVisitor{
clientset: c,
}
// Determine which HistoryViewer we need here
err := elem.Accept(visitor)
if err != nil {
return nil, fmt.Errorf("error retrieving history for %q, %v", kind.String(), err)
}
if visitor.result == nil {
return nil, fmt.Errorf("no history viewer has been implemented for %q", kind.String())
}
return visitor.result, nil
}
type DeploymentHistoryViewer struct {
c kubernetes.Interface
}
// ViewHistory returns a revision-to-replicaset map as the revision history of a deployment
// TODO: this should be a describer
func (h *DeploymentHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
versionedAppsClient := h.c.AppsV1()
deployment, err := versionedAppsClient.Deployments(namespace).Get(name, metav1.GetOptions{})
if err != nil {
return "", fmt.Errorf("failed to retrieve deployment %s: %v", name, err)
}
_, allOldRSs, newRS, err := deploymentutil.GetAllReplicaSets(deployment, versionedAppsClient)
if err != nil {
return "", fmt.Errorf("failed to retrieve replica sets from deployment %s: %v", name, err)
}
allRSs := allOldRSs
if newRS != nil {
allRSs = append(allRSs, newRS)
}
historyInfo := make(map[int64]*corev1.PodTemplateSpec)
for _, rs := range allRSs {
v, err := deploymentutil.Revision(rs)
if err != nil {
continue
}
historyInfo[v] = &rs.Spec.Template
changeCause := getChangeCause(rs)
if historyInfo[v].Annotations == nil {
historyInfo[v].Annotations = make(map[string]string)
}
if len(changeCause) > 0 {
historyInfo[v].Annotations[ChangeCauseAnnotation] = changeCause
}
}
if len(historyInfo) == 0 {
return "No rollout history found.", nil
}
if revision > 0 {
// Print details of a specific revision
template, ok := historyInfo[revision]
if !ok {
return "", fmt.Errorf("unable to find the specified revision")
}
return printTemplate(template)
}
// Sort the revisionToChangeCause map by revision
revisions := make([]int64, 0, len(historyInfo))
for r := range historyInfo {
revisions = append(revisions, r)
}
sliceutil.SortInts64(revisions)
return tabbedString(func(out io.Writer) error {
fmt.Fprintf(out, "REVISION\tCHANGE-CAUSE\n")
for _, r := range revisions {
// Find the change-cause of revision r
changeCause := historyInfo[r].Annotations[ChangeCauseAnnotation]
if len(changeCause) == 0 {
changeCause = "<none>"
}
fmt.Fprintf(out, "%d\t%s\n", r, changeCause)
}
return nil
})
}
func printTemplate(template *corev1.PodTemplateSpec) (string, error) {
buf := bytes.NewBuffer([]byte{})
w := describe.NewPrefixWriter(buf)
describe.DescribePodTemplate(template, w)
return buf.String(), nil
}
type DaemonSetHistoryViewer struct {
c kubernetes.Interface
}
// ViewHistory returns a revision-to-history map as the revision history of a deployment
// TODO: this should be a describer
func (h *DaemonSetHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
ds, history, err := daemonSetHistory(h.c.AppsV1(), namespace, name)
if err != nil {
return "", err
}
historyInfo := make(map[int64]*appsv1.ControllerRevision)
for _, history := range history {
// TODO: for now we assume revisions don't overlap, we may need to handle it
historyInfo[history.Revision] = history
}
if len(historyInfo) == 0 {
return "No rollout history found.", nil
}
// Print details of a specific revision
if revision > 0 {
history, ok := historyInfo[revision]
if !ok {
return "", fmt.Errorf("unable to find the specified revision")
}
dsOfHistory, err := applyDaemonSetHistory(ds, history)
if err != nil {
return "", fmt.Errorf("unable to parse history %s", history.Name)
}
return printTemplate(&dsOfHistory.Spec.Template)
}
// Print an overview of all Revisions
// Sort the revisionToChangeCause map by revision
revisions := make([]int64, 0, len(historyInfo))
for r := range historyInfo {
revisions = append(revisions, r)
}
sliceutil.SortInts64(revisions)
return tabbedString(func(out io.Writer) error {
fmt.Fprintf(out, "REVISION\tCHANGE-CAUSE\n")
for _, r := range revisions {
// Find the change-cause of revision r
changeCause := historyInfo[r].Annotations[ChangeCauseAnnotation]
if len(changeCause) == 0 {
changeCause = "<none>"
}
fmt.Fprintf(out, "%d\t%s\n", r, changeCause)
}
return nil
})
}
type StatefulSetHistoryViewer struct {
c kubernetes.Interface
}
// ViewHistory returns a list of the revision history of a statefulset
// TODO: this should be a describer
// TODO: needs to implement detailed revision view
func (h *StatefulSetHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
_, history, err := statefulSetHistory(h.c.AppsV1(), namespace, name)
if err != nil {
return "", err
}
if len(history) <= 0 {
return "No rollout history found.", nil
}
revisions := make([]int64, len(history))
for _, revision := range history {
revisions = append(revisions, revision.Revision)
}
sliceutil.SortInts64(revisions)
return tabbedString(func(out io.Writer) error {
fmt.Fprintf(out, "REVISION\n")
for _, r := range revisions {
fmt.Fprintf(out, "%d\n", r)
}
return nil
})
}
// controlledHistories returns all ControllerRevisions in namespace that selected by selector and owned by accessor
// TODO: Rename this to controllerHistory when other controllers have been upgraded
func controlledHistoryV1(
apps clientappsv1.AppsV1Interface,
namespace string,
selector labels.Selector,
accessor metav1.Object) ([]*appsv1.ControllerRevision, error) {
var result []*appsv1.ControllerRevision
historyList, err := apps.ControllerRevisions(namespace).List(metav1.ListOptions{LabelSelector: selector.String()})
if err != nil {
return nil, err
}
for i := range historyList.Items {
history := historyList.Items[i]
// Only add history that belongs to the API object
if metav1.IsControlledBy(&history, accessor) {
result = append(result, &history)
}
}
return result, nil
}
// controlledHistories returns all ControllerRevisions in namespace that selected by selector and owned by accessor
func controlledHistory(
apps clientappsv1.AppsV1Interface,
namespace string,
selector labels.Selector,
accessor metav1.Object) ([]*appsv1.ControllerRevision, error) {
var result []*appsv1.ControllerRevision
historyList, err := apps.ControllerRevisions(namespace).List(metav1.ListOptions{LabelSelector: selector.String()})
if err != nil {
return nil, err
}
for i := range historyList.Items {
history := historyList.Items[i]
// Only add history that belongs to the API object
if metav1.IsControlledBy(&history, accessor) {
result = append(result, &history)
}
}
return result, nil
}
// daemonSetHistory returns the DaemonSet named name in namespace and all ControllerRevisions in its history.
func daemonSetHistory(
apps clientappsv1.AppsV1Interface,
namespace, name string) (*appsv1.DaemonSet, []*appsv1.ControllerRevision, error) {
ds, err := apps.DaemonSets(namespace).Get(name, metav1.GetOptions{})
if err != nil {
return nil, nil, fmt.Errorf("failed to retrieve DaemonSet %s: %v", name, err)
}
selector, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector)
if err != nil {
return nil, nil, fmt.Errorf("failed to create selector for DaemonSet %s: %v", ds.Name, err)
}
accessor, err := meta.Accessor(ds)
if err != nil {
return nil, nil, fmt.Errorf("failed to create accessor for DaemonSet %s: %v", ds.Name, err)
}
history, err := controlledHistory(apps, ds.Namespace, selector, accessor)
if err != nil {
return nil, nil, fmt.Errorf("unable to find history controlled by DaemonSet %s: %v", ds.Name, err)
}
return ds, history, nil
}
// statefulSetHistory returns the StatefulSet named name in namespace and all ControllerRevisions in its history.
func statefulSetHistory(
apps clientappsv1.AppsV1Interface,
namespace, name string) (*appsv1.StatefulSet, []*appsv1.ControllerRevision, error) {
sts, err := apps.StatefulSets(namespace).Get(name, metav1.GetOptions{})
if err != nil {
return nil, nil, fmt.Errorf("failed to retrieve Statefulset %s: %s", name, err.Error())
}
selector, err := metav1.LabelSelectorAsSelector(sts.Spec.Selector)
if err != nil {
return nil, nil, fmt.Errorf("failed to create selector for StatefulSet %s: %s", name, err.Error())
}
accessor, err := meta.Accessor(sts)
if err != nil {
return nil, nil, fmt.Errorf("failed to obtain accessor for StatefulSet %s: %s", name, err.Error())
}
history, err := controlledHistoryV1(apps, namespace, selector, accessor)
if err != nil {
return nil, nil, fmt.Errorf("unable to find history controlled by StatefulSet %s: %v", name, err)
}
return sts, history, nil
}
// applyDaemonSetHistory returns a specific revision of DaemonSet by applying the given history to a copy of the given DaemonSet
func applyDaemonSetHistory(ds *appsv1.DaemonSet, history *appsv1.ControllerRevision) (*appsv1.DaemonSet, error) {
clone := ds.DeepCopy()
cloneBytes, err := json.Marshal(clone)
if err != nil {
return nil, err
}
patched, err := strategicpatch.StrategicMergePatch(cloneBytes, history.Data.Raw, clone)
if err != nil {
return nil, err
}
err = json.Unmarshal(patched, clone)
if err != nil {
return nil, err
}
return clone, nil
}
// TODO: copied here until this becomes a describer
func tabbedString(f func(io.Writer) error) (string, error) {
out := new(tabwriter.Writer)
buf := &bytes.Buffer{}
out.Init(buf, 0, 8, 2, ' ', 0)
err := f(out)
if err != nil {
return "", err
}
out.Flush()
str := string(buf.String())
return str, nil
}
// getChangeCause returns the change-cause annotation of the input object
func getChangeCause(obj runtime.Object) string {
accessor, err := meta.Accessor(obj)
if err != nil {
return ""
}
return accessor.GetAnnotations()[ChangeCauseAnnotation]
}

View File

@@ -1,46 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"reflect"
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/fake"
)
var historytests = map[schema.GroupKind]reflect.Type{
{Group: "apps", Kind: "DaemonSet"}: reflect.TypeOf(&DaemonSetHistoryViewer{}),
{Group: "apps", Kind: "StatefulSet"}: reflect.TypeOf(&StatefulSetHistoryViewer{}),
{Group: "apps", Kind: "Deployment"}: reflect.TypeOf(&DeploymentHistoryViewer{}),
}
func TestHistoryViewerFor(t *testing.T) {
fakeClientset := &fake.Clientset{}
for kind, expectedType := range historytests {
result, err := HistoryViewerFor(kind, fakeClientset)
if err != nil {
t.Fatalf("error getting HistoryViewer for a %v: %v", kind.String(), err)
}
if reflect.TypeOf(result) != expectedType {
t.Fatalf("unexpected output type (%v was expected but got %v)", expectedType, reflect.TypeOf(result))
}
}
}

View File

@@ -1,37 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/kubernetes"
)
// historyViewer Returns a HistoryViewer for viewing change history
func historyViewer(restClientGetter genericclioptions.RESTClientGetter, mapping *meta.RESTMapping) (HistoryViewer, error) {
clientConfig, err := restClientGetter.ToRESTConfig()
if err != nil {
return nil, err
}
external, err := kubernetes.NewForConfig(clientConfig)
if err != nil {
return nil, err
}
return HistoryViewerFor(mapping.GroupVersionKind.GroupKind(), external)
}

View File

@@ -1,114 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"time"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest"
)
// LogsForObjectFunc is a function type that can tell you how to get logs for a runtime.object
type LogsForObjectFunc func(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration, allContainers bool) ([]rest.ResponseWrapper, error)
// LogsForObjectFn gives a way to easily override the function for unit testing if needed.
var LogsForObjectFn LogsForObjectFunc = logsForObject
// AttachablePodForObjectFunc is a function type that can tell you how to get the pod for which to attach a given object
type AttachablePodForObjectFunc func(restClientGetter genericclioptions.RESTClientGetter, object runtime.Object, timeout time.Duration) (*v1.Pod, error)
// AttachablePodForObjectFn gives a way to easily override the function for unit testing if needed.
var AttachablePodForObjectFn AttachablePodForObjectFunc = attachablePodForObject
// HistoryViewerFunc is a function type that can tell you how to view change history
type HistoryViewerFunc func(restClientGetter genericclioptions.RESTClientGetter, mapping *meta.RESTMapping) (HistoryViewer, error)
// HistoryViewerFn gives a way to easily override the function for unit testing if needed
var HistoryViewerFn HistoryViewerFunc = historyViewer
// StatusViewerFunc is a function type that can tell you how to print rollout status
type StatusViewerFunc func(mapping *meta.RESTMapping) (StatusViewer, error)
// StatusViewerFn gives a way to easily override the function for unit testing if needed
var StatusViewerFn StatusViewerFunc = statusViewer
// UpdatePodSpecForObjectFunc will call the provided function on the pod spec this object supports,
// return false if no pod spec is supported, or return an error.
type UpdatePodSpecForObjectFunc func(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error)
// UpdatePodSpecForObjectFn gives a way to easily override the function for unit testing if needed
var UpdatePodSpecForObjectFn UpdatePodSpecForObjectFunc = updatePodSpecForObject
// MapBasedSelectorForObjectFunc will call the provided function on mapping the baesd selector for object,
// return "" if object is not supported, or return an error.
type MapBasedSelectorForObjectFunc func(object runtime.Object) (string, error)
// MapBasedSelectorForObjectFn gives a way to easily override the function for unit testing if needed
var MapBasedSelectorForObjectFn MapBasedSelectorForObjectFunc = mapBasedSelectorForObject
// ProtocolsForObjectFunc will call the provided function on the protocols for the object,
// return nil-map if no protocols for the object, or return an error.
type ProtocolsForObjectFunc func(object runtime.Object) (map[string]string, error)
// ProtocolsForObjectFn gives a way to easily override the function for unit testing if needed
var ProtocolsForObjectFn ProtocolsForObjectFunc = protocolsForObject
// PortsForObjectFunc returns the ports associated with the provided object
type PortsForObjectFunc func(object runtime.Object) ([]string, error)
// PortsForObjectFn gives a way to easily override the function for unit testing if needed
var PortsForObjectFn PortsForObjectFunc = portsForObject
// CanBeExposedFunc is a function type that can tell you whether a given GroupKind is capable of being exposed
type CanBeExposedFunc func(kind schema.GroupKind) error
// CanBeExposedFn gives a way to easily override the function for unit testing if needed
var CanBeExposedFn CanBeExposedFunc = canBeExposed
// ObjectPauserFunc is a function type that marks the object in a given info as paused.
type ObjectPauserFunc func(runtime.Object) ([]byte, error)
// ObjectPauserFn gives a way to easily override the function for unit testing if needed.
// Returns the patched object in bytes and any error that occurred during the encoding or
// in case the object is already paused.
var ObjectPauserFn ObjectPauserFunc = defaultObjectPauser
// ObjectResumerFunc is a function type that marks the object in a given info as resumed.
type ObjectResumerFunc func(runtime.Object) ([]byte, error)
// ObjectResumerFn gives a way to easily override the function for unit testing if needed.
// Returns the patched object in bytes and any error that occurred during the encoding or
// in case the object is already resumed.
var ObjectResumerFn ObjectResumerFunc = defaultObjectResumer
// RollbackerFunc gives a way to change the rollback version of the specified RESTMapping type
type RollbackerFunc func(restClientGetter genericclioptions.RESTClientGetter, mapping *meta.RESTMapping) (Rollbacker, error)
// RollbackerFn gives a way to easily override the function for unit testing if needed
var RollbackerFn RollbackerFunc = rollbacker
// ObjectRestarterFunc is a function type that updates an annotation in a deployment to restart it..
type ObjectRestarterFunc func(runtime.Object) ([]byte, error)
// ObjectRestarterFn gives a way to easily override the function for unit testing if needed.
// Returns the patched object in bytes and any error that occurred during the encoding.
var ObjectRestarterFn ObjectRestarterFunc = defaultObjectRestarter

View File

@@ -1,111 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"errors"
"fmt"
"os"
"sort"
"time"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest"
"k8s.io/kubectl/pkg/util/podutils"
)
func logsForObject(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration, allContainers bool) ([]rest.ResponseWrapper, error) {
clientConfig, err := restClientGetter.ToRESTConfig()
if err != nil {
return nil, err
}
clientset, err := corev1client.NewForConfig(clientConfig)
if err != nil {
return nil, err
}
return logsForObjectWithClient(clientset, object, options, timeout, allContainers)
}
// TODO: remove internal clientset once all callers use external versions
// this is split for easy test-ability
func logsForObjectWithClient(clientset corev1client.CoreV1Interface, object, options runtime.Object, timeout time.Duration, allContainers bool) ([]rest.ResponseWrapper, error) {
opts, ok := options.(*corev1.PodLogOptions)
if !ok {
return nil, errors.New("provided options object is not a PodLogOptions")
}
switch t := object.(type) {
case *corev1.PodList:
ret := []rest.ResponseWrapper{}
for i := range t.Items {
currRet, err := logsForObjectWithClient(clientset, &t.Items[i], options, timeout, allContainers)
if err != nil {
return nil, err
}
ret = append(ret, currRet...)
}
return ret, nil
case *corev1.Pod:
// if allContainers is true, then we're going to locate all containers and then iterate through them. At that point, "allContainers" is false
if !allContainers {
return []rest.ResponseWrapper{clientset.Pods(t.Namespace).GetLogs(t.Name, opts)}, nil
}
ret := []rest.ResponseWrapper{}
for _, c := range t.Spec.InitContainers {
currOpts := opts.DeepCopy()
currOpts.Container = c.Name
currRet, err := logsForObjectWithClient(clientset, t, currOpts, timeout, false)
if err != nil {
return nil, err
}
ret = append(ret, currRet...)
}
for _, c := range t.Spec.Containers {
currOpts := opts.DeepCopy()
currOpts.Container = c.Name
currRet, err := logsForObjectWithClient(clientset, t, currOpts, timeout, false)
if err != nil {
return nil, err
}
ret = append(ret, currRet...)
}
return ret, nil
}
namespace, selector, err := SelectorsForObject(object)
if err != nil {
return nil, fmt.Errorf("cannot get the logs from %T: %v", object, err)
}
sortBy := func(pods []*corev1.Pod) sort.Interface { return podutils.ByLogging(pods) }
pod, numPods, err := GetFirstPod(clientset, namespace, selector.String(), timeout, sortBy)
if err != nil {
return nil, err
}
if numPods > 1 {
fmt.Fprintf(os.Stderr, "Found %v pods, using pod/%v\n", numPods, pod.Name)
}
return logsForObjectWithClient(clientset, pod, options, timeout, allContainers)
}

View File

@@ -1,259 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"reflect"
"testing"
"time"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
fakeexternal "k8s.io/client-go/kubernetes/fake"
testclient "k8s.io/client-go/testing"
)
var (
podsResource = schema.GroupVersionResource{Version: "v1", Resource: "pods"}
podsKind = schema.GroupVersionKind{Version: "v1", Kind: "Pod"}
)
func TestLogsForObject(t *testing.T) {
tests := []struct {
name string
obj runtime.Object
opts *corev1.PodLogOptions
allContainers bool
pods []runtime.Object
actions []testclient.Action
}{
{
name: "pod logs",
obj: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
getLogsAction("test", nil),
},
},
{
name: "pod logs: all containers",
obj: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{
{Name: "initc1"},
{Name: "initc2"},
},
Containers: []corev1.Container{
{Name: "c1"},
{Name: "c2"},
},
},
},
opts: &corev1.PodLogOptions{},
allContainers: true,
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
getLogsAction("test", &corev1.PodLogOptions{Container: "initc1"}),
getLogsAction("test", &corev1.PodLogOptions{Container: "initc2"}),
getLogsAction("test", &corev1.PodLogOptions{Container: "c1"}),
getLogsAction("test", &corev1.PodLogOptions{Container: "c2"}),
},
},
{
name: "pods list logs",
obj: &corev1.PodList{
Items: []corev1.Pod{
{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{
{Name: "initc1"},
{Name: "initc2"},
},
Containers: []corev1.Container{
{Name: "c1"},
{Name: "c2"},
},
},
},
},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
getLogsAction("test", nil),
},
},
{
name: "pods list logs: all containers",
obj: &corev1.PodList{
Items: []corev1.Pod{
{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{
{Name: "initc1"},
{Name: "initc2"},
},
Containers: []corev1.Container{
{Name: "c1"},
{Name: "c2"},
},
},
},
},
},
opts: &corev1.PodLogOptions{},
allContainers: true,
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
getLogsAction("test", &corev1.PodLogOptions{Container: "initc1"}),
getLogsAction("test", &corev1.PodLogOptions{Container: "initc2"}),
getLogsAction("test", &corev1.PodLogOptions{Container: "c1"}),
getLogsAction("test", &corev1.PodLogOptions{Container: "c2"}),
},
},
{
name: "replication controller logs",
obj: &corev1.ReplicationController{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: corev1.ReplicationControllerSpec{
Selector: map[string]string{"foo": "bar"},
},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
testclient.NewListAction(podsResource, podsKind, "test", metav1.ListOptions{LabelSelector: "foo=bar"}),
getLogsAction("test", nil),
},
},
{
name: "replica set logs",
obj: &extensionsv1beta1.ReplicaSet{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: extensionsv1beta1.ReplicaSetSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
testclient.NewListAction(podsResource, podsKind, "test", metav1.ListOptions{LabelSelector: "foo=bar"}),
getLogsAction("test", nil),
},
},
{
name: "deployment logs",
obj: &extensionsv1beta1.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: extensionsv1beta1.DeploymentSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
testclient.NewListAction(podsResource, podsKind, "test", metav1.ListOptions{LabelSelector: "foo=bar"}),
getLogsAction("test", nil),
},
},
{
name: "job logs",
obj: &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: batchv1.JobSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
testclient.NewListAction(podsResource, podsKind, "test", metav1.ListOptions{LabelSelector: "foo=bar"}),
getLogsAction("test", nil),
},
},
{
name: "stateful set logs",
obj: &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: appsv1.StatefulSetSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
testclient.NewListAction(podsResource, podsKind, "test", metav1.ListOptions{LabelSelector: "foo=bar"}),
getLogsAction("test", nil),
},
},
}
for _, test := range tests {
fakeClientset := fakeexternal.NewSimpleClientset(test.pods...)
_, err := logsForObjectWithClient(fakeClientset.CoreV1(), test.obj, test.opts, 20*time.Second, test.allContainers)
if err != nil {
t.Errorf("%s: unexpected error: %v", test.name, err)
continue
}
for i := range test.actions {
if len(fakeClientset.Actions()) < i {
t.Errorf("%s: action %d does not exists in actual actions: %#v",
test.name, i, fakeClientset.Actions())
continue
}
got := fakeClientset.Actions()[i]
want := test.actions[i]
if !reflect.DeepEqual(got, want) {
t.Errorf("%s: unexpected action: %s", test.name, diff.ObjectDiff(got, want))
}
}
}
}
func testPod() runtime.Object {
return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "test",
Labels: map[string]string{"foo": "bar"},
},
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyAlways,
DNSPolicy: corev1.DNSClusterFirst,
Containers: []corev1.Container{
{Name: "c1"},
{Name: "c2"},
},
},
}
}
func getLogsAction(namespace string, opts *corev1.PodLogOptions) testclient.Action {
action := testclient.GenericActionImpl{}
action.Verb = "get"
action.Namespace = namespace
action.Resource = podsResource
action.Subresource = "log"
action.Value = opts
return action
}

View File

@@ -1,160 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"fmt"
"strings"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
)
// mapBasedSelectorForObject returns the map-based selector associated with the provided object. If a
// new set-based selector is provided, an error is returned if the selector cannot be converted to a
// map-based selector
func mapBasedSelectorForObject(object runtime.Object) (string, error) {
// TODO: replace with a swagger schema based approach (identify pod selector via schema introspection)
switch t := object.(type) {
case *corev1.ReplicationController:
return MakeLabels(t.Spec.Selector), nil
case *corev1.Pod:
if len(t.Labels) == 0 {
return "", fmt.Errorf("the pod has no labels and cannot be exposed")
}
return MakeLabels(t.Labels), nil
case *corev1.Service:
if t.Spec.Selector == nil {
return "", fmt.Errorf("the service has no pod selector set")
}
return MakeLabels(t.Spec.Selector), nil
case *extensionsv1beta1.Deployment:
// "extensions" deployments use pod template labels if selector is not set.
var labels map[string]string
if t.Spec.Selector != nil {
// TODO(madhusudancs): Make this smarter by admitting MatchExpressions with Equals
// operator, DoubleEquals operator and In operator with only one element in the set.
if len(t.Spec.Selector.MatchExpressions) > 0 {
return "", fmt.Errorf("couldn't convert expressions - \"%+v\" to map-based selector format", t.Spec.Selector.MatchExpressions)
}
labels = t.Spec.Selector.MatchLabels
} else {
labels = t.Spec.Template.Labels
}
if len(labels) == 0 {
return "", fmt.Errorf("the deployment has no labels or selectors and cannot be exposed")
}
return MakeLabels(labels), nil
case *appsv1.Deployment:
// "apps" deployments must have the selector set.
if t.Spec.Selector == nil || len(t.Spec.Selector.MatchLabels) == 0 {
return "", fmt.Errorf("invalid deployment: no selectors, therefore cannot be exposed")
}
// TODO(madhusudancs): Make this smarter by admitting MatchExpressions with Equals
// operator, DoubleEquals operator and In operator with only one element in the set.
if len(t.Spec.Selector.MatchExpressions) > 0 {
return "", fmt.Errorf("couldn't convert expressions - \"%+v\" to map-based selector format", t.Spec.Selector.MatchExpressions)
}
return MakeLabels(t.Spec.Selector.MatchLabels), nil
case *appsv1beta2.Deployment:
// "apps" deployments must have the selector set.
if t.Spec.Selector == nil || len(t.Spec.Selector.MatchLabels) == 0 {
return "", fmt.Errorf("invalid deployment: no selectors, therefore cannot be exposed")
}
// TODO(madhusudancs): Make this smarter by admitting MatchExpressions with Equals
// operator, DoubleEquals operator and In operator with only one element in the set.
if len(t.Spec.Selector.MatchExpressions) > 0 {
return "", fmt.Errorf("couldn't convert expressions - \"%+v\" to map-based selector format", t.Spec.Selector.MatchExpressions)
}
return MakeLabels(t.Spec.Selector.MatchLabels), nil
case *appsv1beta1.Deployment:
// "apps" deployments must have the selector set.
if t.Spec.Selector == nil || len(t.Spec.Selector.MatchLabels) == 0 {
return "", fmt.Errorf("invalid deployment: no selectors, therefore cannot be exposed")
}
// TODO(madhusudancs): Make this smarter by admitting MatchExpressions with Equals
// operator, DoubleEquals operator and In operator with only one element in the set.
if len(t.Spec.Selector.MatchExpressions) > 0 {
return "", fmt.Errorf("couldn't convert expressions - \"%+v\" to map-based selector format", t.Spec.Selector.MatchExpressions)
}
return MakeLabels(t.Spec.Selector.MatchLabels), nil
case *extensionsv1beta1.ReplicaSet:
// "extensions" replicasets use pod template labels if selector is not set.
var labels map[string]string
if t.Spec.Selector != nil {
// TODO(madhusudancs): Make this smarter by admitting MatchExpressions with Equals
// operator, DoubleEquals operator and In operator with only one element in the set.
if len(t.Spec.Selector.MatchExpressions) > 0 {
return "", fmt.Errorf("couldn't convert expressions - \"%+v\" to map-based selector format", t.Spec.Selector.MatchExpressions)
}
labels = t.Spec.Selector.MatchLabels
} else {
labels = t.Spec.Template.Labels
}
if len(labels) == 0 {
return "", fmt.Errorf("the replica set has no labels or selectors and cannot be exposed")
}
return MakeLabels(labels), nil
case *appsv1.ReplicaSet:
// "apps" replicasets must have the selector set.
if t.Spec.Selector == nil || len(t.Spec.Selector.MatchLabels) == 0 {
return "", fmt.Errorf("invalid replicaset: no selectors, therefore cannot be exposed")
}
// TODO(madhusudancs): Make this smarter by admitting MatchExpressions with Equals
// operator, DoubleEquals operator and In operator with only one element in the set.
if len(t.Spec.Selector.MatchExpressions) > 0 {
return "", fmt.Errorf("couldn't convert expressions - \"%+v\" to map-based selector format", t.Spec.Selector.MatchExpressions)
}
return MakeLabels(t.Spec.Selector.MatchLabels), nil
case *appsv1beta2.ReplicaSet:
// "apps" replicasets must have the selector set.
if t.Spec.Selector == nil || len(t.Spec.Selector.MatchLabels) == 0 {
return "", fmt.Errorf("invalid replicaset: no selectors, therefore cannot be exposed")
}
// TODO(madhusudancs): Make this smarter by admitting MatchExpressions with Equals
// operator, DoubleEquals operator and In operator with only one element in the set.
if len(t.Spec.Selector.MatchExpressions) > 0 {
return "", fmt.Errorf("couldn't convert expressions - \"%+v\" to map-based selector format", t.Spec.Selector.MatchExpressions)
}
return MakeLabels(t.Spec.Selector.MatchLabels), nil
default:
return "", fmt.Errorf("cannot extract pod selector from %T", object)
}
}
func MakeLabels(labels map[string]string) string {
out := []string{}
for key, value := range labels {
out = append(out, fmt.Sprintf("%s=%s", key, value))
}
return strings.Join(out, ",")
}

View File

@@ -1,489 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"testing"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
func TestMapBasedSelectorForObject(t *testing.T) {
tests := []struct {
object runtime.Object
expectSelector string
expectErr bool
}{
{
object: &corev1.ReplicationController{
Spec: corev1.ReplicationControllerSpec{
Selector: map[string]string{
"foo": "bar",
},
},
},
expectSelector: "foo=bar",
},
{
object: &corev1.Pod{},
expectErr: true,
},
{
object: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
},
expectSelector: "foo=bar",
},
{
object: &corev1.Service{
Spec: corev1.ServiceSpec{
Selector: map[string]string{
"foo": "bar",
},
},
},
expectSelector: "foo=bar",
},
{
object: &corev1.Service{},
expectErr: true,
},
// extensions/v1beta1 Deployment with labels and selectors
{
object: &extensionsv1beta1.Deployment{
Spec: extensionsv1beta1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "bar",
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
},
},
},
expectSelector: "foo=bar",
},
// extensions/v1beta1 Deployment with only labels (no selectors) -- use labels
{
object: &extensionsv1beta1.Deployment{
Spec: extensionsv1beta1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
},
},
},
expectSelector: "foo=bar",
},
// extensions/v1beta1 Deployment with bad selector
{
object: &extensionsv1beta1.Deployment{
Spec: extensionsv1beta1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "foo",
},
},
},
},
},
expectErr: true,
},
// apps/v1 Deployment with labels and selectors
{
object: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
},
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "bar",
},
},
},
},
expectSelector: "foo=bar",
},
// apps/v1 Deployment with only labels (no selectors) -- error
{
object: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
},
},
},
expectErr: true,
},
// apps/v1 Deployment with no labels or selectors -- error
{
object: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{},
},
expectErr: true,
},
// apps/v1 Deployment with empty labels -- error
{
object: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{}, // Empty labels map
},
},
},
},
expectErr: true,
},
// apps/v1beta2 Deployment with labels and selectors
{
object: &appsv1beta2.Deployment{
Spec: appsv1beta2.DeploymentSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
},
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "bar",
},
},
},
},
expectSelector: "foo=bar",
},
// apps/v1beta2 Deployment with only labels (no selectors) -- error
{
object: &appsv1beta2.Deployment{
Spec: appsv1beta2.DeploymentSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
},
},
},
expectErr: true,
},
// apps/v1beta2 Deployment with no labels or selectors -- error
{
object: &appsv1beta2.Deployment{
Spec: appsv1beta2.DeploymentSpec{},
},
expectErr: true,
},
// apps/v1beta1 Deployment with labels and selectors
{
object: &appsv1beta1.Deployment{
Spec: appsv1beta1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
},
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "bar",
},
},
},
},
expectSelector: "foo=bar",
},
// apps/v1beta1 Deployment with only labels (no selectors) -- error
{
object: &appsv1beta1.Deployment{
Spec: appsv1beta1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
},
},
},
expectErr: true,
},
// apps/v1beta1 Deployment with no labels or selectors -- error
{
object: &appsv1beta1.Deployment{
Spec: appsv1beta1.DeploymentSpec{},
},
expectErr: true,
},
// extensions/v1beta1 ReplicaSet with labels and selectors
{
object: &extensionsv1beta1.ReplicaSet{
Spec: extensionsv1beta1.ReplicaSetSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
},
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "bar",
},
},
},
},
expectSelector: "foo=bar",
},
// extensions/v1beta1 ReplicaSet with only labels -- no selectors; use labels
{
object: &extensionsv1beta1.ReplicaSet{
Spec: extensionsv1beta1.ReplicaSetSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
},
},
},
expectSelector: "foo=bar",
},
// extensions/v1beta1 ReplicaSet with bad label selector -- error
{
object: &extensionsv1beta1.ReplicaSet{
Spec: extensionsv1beta1.ReplicaSetSpec{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "foo",
},
},
},
},
},
expectErr: true,
},
// apps/v1 ReplicaSet with labels and selectors
{
object: &appsv1.ReplicaSet{
Spec: appsv1.ReplicaSetSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
},
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "bar",
},
},
},
},
expectSelector: "foo=bar",
},
// apps/v1 ReplicaSet with only labels (no selectors) -- error
{
object: &appsv1.ReplicaSet{
Spec: appsv1.ReplicaSetSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
},
},
},
expectErr: true,
},
// apps/v1beta2 ReplicaSet with labels and selectors
{
object: &appsv1beta2.ReplicaSet{
Spec: appsv1beta2.ReplicaSetSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
},
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "bar",
},
},
},
},
expectSelector: "foo=bar",
},
// apps/v1beta2 ReplicaSet with only labels (no selectors) -- error
{
object: &appsv1beta2.ReplicaSet{
Spec: appsv1beta2.ReplicaSetSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
},
},
},
expectErr: true,
},
// Node can not be exposed -- error
{
object: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "bar",
},
},
},
},
expectSelector: "foo=bar",
},
{
object: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "foo",
},
},
},
},
},
expectErr: true,
},
{
object: &appsv1.ReplicaSet{
Spec: appsv1.ReplicaSetSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "bar",
},
},
},
},
expectSelector: "foo=bar",
},
{
object: &appsv1.ReplicaSet{
Spec: appsv1.ReplicaSetSpec{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "foo",
},
},
},
},
},
expectErr: true,
},
{
object: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{
Selector: nil,
},
},
expectErr: true,
},
{
object: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{
Selector: nil,
},
},
expectErr: true,
},
{
object: &appsv1.ReplicaSet{
Spec: appsv1.ReplicaSetSpec{
Selector: nil,
},
},
expectErr: true,
},
{
object: &appsv1.ReplicaSet{
Spec: appsv1.ReplicaSetSpec{
Selector: nil,
},
},
expectErr: true,
},
{
object: &corev1.Node{},
expectErr: true,
},
}
for _, test := range tests {
actual, err := mapBasedSelectorForObject(test.object)
if test.expectErr && err == nil {
t.Error("unexpected non-error")
}
if !test.expectErr && err != nil {
t.Errorf("unexpected error: %v", err)
}
if actual != test.expectSelector {
t.Errorf("expected selector %q, but got %q", test.expectSelector, actual)
}
}
}

View File

@@ -1,65 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"errors"
"fmt"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubectl/pkg/scheme"
)
// Currently only supports Deployments.
func defaultObjectPauser(obj runtime.Object) ([]byte, error) {
switch obj := obj.(type) {
case *extensionsv1beta1.Deployment:
if obj.Spec.Paused {
return nil, errors.New("is already paused")
}
obj.Spec.Paused = true
return runtime.Encode(scheme.Codecs.LegacyCodec(extensionsv1beta1.SchemeGroupVersion), obj)
case *appsv1.Deployment:
if obj.Spec.Paused {
return nil, errors.New("is already paused")
}
obj.Spec.Paused = true
return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion), obj)
case *appsv1beta2.Deployment:
if obj.Spec.Paused {
return nil, errors.New("is already paused")
}
obj.Spec.Paused = true
return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1beta2.SchemeGroupVersion), obj)
case *appsv1beta1.Deployment:
if obj.Spec.Paused {
return nil, errors.New("is already paused")
}
obj.Spec.Paused = true
return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1beta1.SchemeGroupVersion), obj)
default:
return nil, fmt.Errorf("pausing is not supported")
}
}

View File

@@ -1,72 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"bytes"
"testing"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
)
func TestDefaultObjectPauser(t *testing.T) {
tests := []struct {
object runtime.Object
expect []byte
expectErr bool
}{
{
object: &extensionsv1beta1.Deployment{
Spec: extensionsv1beta1.DeploymentSpec{
Paused: false,
},
},
expect: []byte(`paused":true`),
expectErr: false,
},
{
object: &extensionsv1beta1.Deployment{
Spec: extensionsv1beta1.DeploymentSpec{
Paused: true,
},
},
expectErr: true,
},
{
object: &extensionsv1beta1.ReplicaSet{},
expectErr: true,
},
}
for _, test := range tests {
actual, err := defaultObjectPauser(test.object)
if test.expectErr {
if err == nil {
t.Error("unexpected non-error")
}
continue
}
if !test.expectErr && err != nil {
t.Errorf("unexpected error: %v", err)
continue
}
if !bytes.Contains(actual, test.expect) {
t.Errorf("expected %s, but got %s", test.expect, actual)
}
}
}

View File

@@ -1,119 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"errors"
"fmt"
"time"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubectl/pkg/scheme"
)
func defaultObjectRestarter(obj runtime.Object) ([]byte, error) {
switch obj := obj.(type) {
case *extensionsv1beta1.Deployment:
if obj.Spec.Paused {
return nil, errors.New("can't restart paused deployment (run rollout resume first)")
}
if obj.Spec.Template.ObjectMeta.Annotations == nil {
obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
}
obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
return runtime.Encode(scheme.Codecs.LegacyCodec(extensionsv1beta1.SchemeGroupVersion), obj)
case *appsv1.Deployment:
if obj.Spec.Paused {
return nil, errors.New("can't restart paused deployment (run rollout resume first)")
}
if obj.Spec.Template.ObjectMeta.Annotations == nil {
obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
}
obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion), obj)
case *appsv1beta2.Deployment:
if obj.Spec.Paused {
return nil, errors.New("can't restart paused deployment (run rollout resume first)")
}
if obj.Spec.Template.ObjectMeta.Annotations == nil {
obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
}
obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1beta2.SchemeGroupVersion), obj)
case *appsv1beta1.Deployment:
if obj.Spec.Paused {
return nil, errors.New("can't restart paused deployment (run rollout resume first)")
}
if obj.Spec.Template.ObjectMeta.Annotations == nil {
obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
}
obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1beta1.SchemeGroupVersion), obj)
case *extensionsv1beta1.DaemonSet:
if obj.Spec.Template.ObjectMeta.Annotations == nil {
obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
}
obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
return runtime.Encode(scheme.Codecs.LegacyCodec(extensionsv1beta1.SchemeGroupVersion), obj)
case *appsv1.DaemonSet:
if obj.Spec.Template.ObjectMeta.Annotations == nil {
obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
}
obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion), obj)
case *appsv1beta2.DaemonSet:
if obj.Spec.Template.ObjectMeta.Annotations == nil {
obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
}
obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1beta2.SchemeGroupVersion), obj)
case *appsv1.StatefulSet:
if obj.Spec.Template.ObjectMeta.Annotations == nil {
obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
}
obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion), obj)
case *appsv1beta1.StatefulSet:
if obj.Spec.Template.ObjectMeta.Annotations == nil {
obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
}
obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1beta1.SchemeGroupVersion), obj)
case *appsv1beta2.StatefulSet:
if obj.Spec.Template.ObjectMeta.Annotations == nil {
obj.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
}
obj.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1beta2.SchemeGroupVersion), obj)
default:
return nil, fmt.Errorf("restarting is not supported")
}
}

View File

@@ -1,64 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"errors"
"fmt"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubectl/pkg/scheme"
)
func defaultObjectResumer(obj runtime.Object) ([]byte, error) {
switch obj := obj.(type) {
case *extensionsv1beta1.Deployment:
if !obj.Spec.Paused {
return nil, errors.New("is not paused")
}
obj.Spec.Paused = false
return runtime.Encode(scheme.Codecs.LegacyCodec(extensionsv1beta1.SchemeGroupVersion), obj)
case *appsv1.Deployment:
if !obj.Spec.Paused {
return nil, errors.New("is not paused")
}
obj.Spec.Paused = false
return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion), obj)
case *appsv1beta2.Deployment:
if !obj.Spec.Paused {
return nil, errors.New("is not paused")
}
obj.Spec.Paused = false
return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1beta2.SchemeGroupVersion), obj)
case *appsv1beta1.Deployment:
if !obj.Spec.Paused {
return nil, errors.New("is not paused")
}
obj.Spec.Paused = false
return runtime.Encode(scheme.Codecs.LegacyCodec(appsv1beta1.SchemeGroupVersion), obj)
default:
return nil, fmt.Errorf("resuming is not supported")
}
}

View File

@@ -1,72 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"bytes"
"testing"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
)
func TestDefaultObjectResumer(t *testing.T) {
tests := []struct {
object runtime.Object
notHave []byte
expectErr bool
}{
{
object: &extensionsv1beta1.Deployment{
Spec: extensionsv1beta1.DeploymentSpec{
Paused: true,
},
},
notHave: []byte(`paused":true`),
expectErr: false,
},
{
object: &extensionsv1beta1.Deployment{
Spec: extensionsv1beta1.DeploymentSpec{
Paused: false,
},
},
expectErr: true,
},
{
object: &extensionsv1beta1.ReplicaSet{},
expectErr: true,
},
}
for _, test := range tests {
actual, err := defaultObjectResumer(test.object)
if test.expectErr {
if err == nil {
t.Error("unexpected non-error")
}
continue
}
if !test.expectErr && err != nil {
t.Errorf("unexpected error: %v", err)
continue
}
if bytes.Contains(actual, test.notHave) {
t.Errorf("expected to not have %s, but got %s", test.notHave, actual)
}
}
}

View File

@@ -1,78 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"fmt"
"strconv"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
)
func portsForObject(object runtime.Object) ([]string, error) {
switch t := object.(type) {
case *corev1.ReplicationController:
return getPorts(t.Spec.Template.Spec), nil
case *corev1.Pod:
return getPorts(t.Spec), nil
case *corev1.Service:
return getServicePorts(t.Spec), nil
case *extensionsv1beta1.Deployment:
return getPorts(t.Spec.Template.Spec), nil
case *appsv1.Deployment:
return getPorts(t.Spec.Template.Spec), nil
case *appsv1beta2.Deployment:
return getPorts(t.Spec.Template.Spec), nil
case *appsv1beta1.Deployment:
return getPorts(t.Spec.Template.Spec), nil
case *extensionsv1beta1.ReplicaSet:
return getPorts(t.Spec.Template.Spec), nil
case *appsv1.ReplicaSet:
return getPorts(t.Spec.Template.Spec), nil
case *appsv1beta2.ReplicaSet:
return getPorts(t.Spec.Template.Spec), nil
default:
return nil, fmt.Errorf("cannot extract ports from %T", object)
}
}
func getPorts(spec corev1.PodSpec) []string {
result := []string{}
for _, container := range spec.Containers {
for _, port := range container.Ports {
result = append(result, strconv.Itoa(int(port.ContainerPort)))
}
}
return result
}
func getServicePorts(spec corev1.ServiceSpec) []string {
result := []string{}
for _, servicePort := range spec.Ports {
result = append(result, strconv.Itoa(int(servicePort.Port)))
}
return result
}

View File

@@ -1,140 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"testing"
"reflect"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
)
func TestPortsForObject(t *testing.T) {
tests := []struct {
object runtime.Object
expectErr bool
}{
{
object: &corev1.Pod{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Ports: []corev1.ContainerPort{
{
ContainerPort: 101,
},
},
},
},
},
},
},
{
object: &corev1.Service{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Port: 101,
},
},
},
},
},
{
object: &corev1.ReplicationController{
Spec: corev1.ReplicationControllerSpec{
Template: &corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Ports: []corev1.ContainerPort{
{
ContainerPort: 101,
},
},
},
},
},
},
},
},
},
{
object: &extensionsv1beta1.Deployment{
Spec: extensionsv1beta1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Ports: []corev1.ContainerPort{
{
ContainerPort: 101,
},
},
},
},
},
},
},
},
},
{
object: &extensionsv1beta1.ReplicaSet{
Spec: extensionsv1beta1.ReplicaSetSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Ports: []corev1.ContainerPort{
{
ContainerPort: 101,
},
},
},
},
},
},
},
},
},
{
object: &corev1.Node{},
expectErr: true,
},
}
expectedPorts := []string{"101"}
for _, test := range tests {
actual, err := portsForObject(test.object)
if test.expectErr {
if err == nil {
t.Error("unexpected non-error")
}
continue
}
if !test.expectErr && err != nil {
t.Errorf("unexpected error: %v", err)
continue
}
if !reflect.DeepEqual(actual, expectedPorts) {
t.Errorf("expected ports %v, but got %v", expectedPorts, actual)
}
}
}

View File

@@ -1,89 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"fmt"
"strconv"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
)
func protocolsForObject(object runtime.Object) (map[string]string, error) {
// TODO: replace with a swagger schema based approach (identify pod selector via schema introspection)
switch t := object.(type) {
case *corev1.ReplicationController:
return getProtocols(t.Spec.Template.Spec), nil
case *corev1.Pod:
return getProtocols(t.Spec), nil
case *corev1.Service:
return getServiceProtocols(t.Spec), nil
case *extensionsv1beta1.Deployment:
return getProtocols(t.Spec.Template.Spec), nil
case *appsv1.Deployment:
return getProtocols(t.Spec.Template.Spec), nil
case *appsv1beta2.Deployment:
return getProtocols(t.Spec.Template.Spec), nil
case *appsv1beta1.Deployment:
return getProtocols(t.Spec.Template.Spec), nil
case *extensionsv1beta1.ReplicaSet:
return getProtocols(t.Spec.Template.Spec), nil
case *appsv1.ReplicaSet:
return getProtocols(t.Spec.Template.Spec), nil
case *appsv1beta2.ReplicaSet:
return getProtocols(t.Spec.Template.Spec), nil
default:
return nil, fmt.Errorf("cannot extract protocols from %T", object)
}
}
func getProtocols(spec corev1.PodSpec) map[string]string {
result := make(map[string]string)
for _, container := range spec.Containers {
for _, port := range container.Ports {
// Empty protocol must be defaulted (TCP)
if len(port.Protocol) == 0 {
port.Protocol = corev1.ProtocolTCP
}
result[strconv.Itoa(int(port.ContainerPort))] = string(port.Protocol)
}
}
return result
}
// Extracts the protocols exposed by a service from the given service spec.
func getServiceProtocols(spec corev1.ServiceSpec) map[string]string {
result := make(map[string]string)
for _, servicePort := range spec.Ports {
// Empty protocol must be defaulted (TCP)
if len(servicePort.Protocol) == 0 {
servicePort.Protocol = corev1.ProtocolTCP
}
result[strconv.Itoa(int(servicePort.Port))] = string(servicePort.Protocol)
}
return result
}

View File

@@ -1,173 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"testing"
"reflect"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
)
func TestProtocolsForObject(t *testing.T) {
tests := []struct {
object runtime.Object
expectErr bool
}{
{
object: &corev1.Pod{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Ports: []corev1.ContainerPort{
{
ContainerPort: 101,
Protocol: "TCP",
},
},
},
},
},
},
},
// No protocol--should default to TCP.
{
object: &corev1.Pod{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Ports: []corev1.ContainerPort{
{
ContainerPort: 101,
},
},
},
},
},
},
},
{
object: &corev1.Service{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Port: 101,
Protocol: "TCP",
},
},
},
},
},
// No protocol for service port--default to TCP
{
object: &corev1.Service{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Port: 101,
},
},
},
},
},
{
object: &corev1.ReplicationController{
Spec: corev1.ReplicationControllerSpec{
Template: &corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Ports: []corev1.ContainerPort{
{
ContainerPort: 101,
Protocol: "TCP",
},
},
},
},
},
},
},
},
},
{
object: &extensionsv1beta1.Deployment{
Spec: extensionsv1beta1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Ports: []corev1.ContainerPort{
{
ContainerPort: 101,
Protocol: "TCP",
},
},
},
},
},
},
},
},
},
{
object: &extensionsv1beta1.ReplicaSet{
Spec: extensionsv1beta1.ReplicaSetSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Ports: []corev1.ContainerPort{
{
ContainerPort: 101,
Protocol: "TCP",
},
},
},
},
},
},
},
},
},
{
object: &corev1.Node{},
expectErr: true,
},
}
expectedPorts := map[string]string{"101": "TCP"}
for _, test := range tests {
actual, err := protocolsForObject(test.object)
if test.expectErr {
if err == nil {
t.Error("unexpected non-error")
}
continue
}
if !test.expectErr && err != nil {
t.Errorf("unexpected error: %v", err)
continue
}
if !reflect.DeepEqual(actual, expectedPorts) {
t.Errorf("expected ports %v, but got %v", expectedPorts, actual)
}
}
}

View File

@@ -1,486 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"bytes"
"fmt"
"sort"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/client-go/kubernetes"
"k8s.io/kubectl/pkg/apps"
"k8s.io/kubectl/pkg/scheme"
deploymentutil "k8s.io/kubectl/pkg/util/deployment"
)
const (
rollbackSuccess = "rolled back"
rollbackSkipped = "skipped rollback"
)
// Rollbacker provides an interface for resources that can be rolled back.
type Rollbacker interface {
Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRun bool) (string, error)
}
type RollbackVisitor struct {
clientset kubernetes.Interface
result Rollbacker
}
func (v *RollbackVisitor) VisitDeployment(elem apps.GroupKindElement) {
v.result = &DeploymentRollbacker{v.clientset}
}
func (v *RollbackVisitor) VisitStatefulSet(kind apps.GroupKindElement) {
v.result = &StatefulSetRollbacker{v.clientset}
}
func (v *RollbackVisitor) VisitDaemonSet(kind apps.GroupKindElement) {
v.result = &DaemonSetRollbacker{v.clientset}
}
func (v *RollbackVisitor) VisitJob(kind apps.GroupKindElement) {}
func (v *RollbackVisitor) VisitPod(kind apps.GroupKindElement) {}
func (v *RollbackVisitor) VisitReplicaSet(kind apps.GroupKindElement) {}
func (v *RollbackVisitor) VisitReplicationController(kind apps.GroupKindElement) {}
func (v *RollbackVisitor) VisitCronJob(kind apps.GroupKindElement) {}
// RollbackerFor returns an implementation of Rollbacker interface for the given schema kind
func RollbackerFor(kind schema.GroupKind, c kubernetes.Interface) (Rollbacker, error) {
elem := apps.GroupKindElement(kind)
visitor := &RollbackVisitor{
clientset: c,
}
err := elem.Accept(visitor)
if err != nil {
return nil, fmt.Errorf("error retrieving rollbacker for %q, %v", kind.String(), err)
}
if visitor.result == nil {
return nil, fmt.Errorf("no rollbacker has been implemented for %q", kind)
}
return visitor.result, nil
}
type DeploymentRollbacker struct {
c kubernetes.Interface
}
func (r *DeploymentRollbacker) Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRun bool) (string, error) {
if toRevision < 0 {
return "", revisionNotFoundErr(toRevision)
}
accessor, err := meta.Accessor(obj)
if err != nil {
return "", fmt.Errorf("failed to create accessor for kind %v: %s", obj.GetObjectKind(), err.Error())
}
name := accessor.GetName()
namespace := accessor.GetNamespace()
// TODO: Fix this after kubectl has been removed from core. It is not possible to convert the runtime.Object
// to the external appsv1 Deployment without round-tripping through an internal version of Deployment. We're
// currently getting rid of all internal versions of resources. So we specifically request the appsv1 version
// here. This follows the same pattern as for DaemonSet and StatefulSet.
deployment, err := r.c.AppsV1().Deployments(namespace).Get(name, metav1.GetOptions{})
if err != nil {
return "", fmt.Errorf("failed to retrieve Deployment %s: %v", name, err)
}
rsForRevision, err := deploymentRevision(deployment, r.c, toRevision)
if err != nil {
return "", err
}
if dryRun {
return printTemplate(&rsForRevision.Spec.Template)
}
if deployment.Spec.Paused {
return "", fmt.Errorf("you cannot rollback a paused deployment; resume it first with 'kubectl rollout resume deployment/%s' and try again", name)
}
// Skip if the revision already matches current Deployment
if equalIgnoreHash(&rsForRevision.Spec.Template, &deployment.Spec.Template) {
return fmt.Sprintf("%s (current template already matches revision %d)", rollbackSkipped, toRevision), nil
}
// remove hash label before patching back into the deployment
delete(rsForRevision.Spec.Template.Labels, appsv1.DefaultDeploymentUniqueLabelKey)
// compute deployment annotations
annotations := map[string]string{}
for k := range annotationsToSkip {
if v, ok := deployment.Annotations[k]; ok {
annotations[k] = v
}
}
for k, v := range rsForRevision.Annotations {
if !annotationsToSkip[k] {
annotations[k] = v
}
}
// make patch to restore
patchType, patch, err := getDeploymentPatch(&rsForRevision.Spec.Template, annotations)
if err != nil {
return "", fmt.Errorf("failed restoring revision %d: %v", toRevision, err)
}
// Restore revision
if _, err = r.c.AppsV1().Deployments(namespace).Patch(name, patchType, patch); err != nil {
return "", fmt.Errorf("failed restoring revision %d: %v", toRevision, err)
}
return rollbackSuccess, nil
}
// equalIgnoreHash returns true if two given podTemplateSpec are equal, ignoring the diff in value of Labels[pod-template-hash]
// We ignore pod-template-hash because:
// 1. The hash result would be different upon podTemplateSpec API changes
// (e.g. the addition of a new field will cause the hash code to change)
// 2. The deployment template won't have hash labels
func equalIgnoreHash(template1, template2 *corev1.PodTemplateSpec) bool {
t1Copy := template1.DeepCopy()
t2Copy := template2.DeepCopy()
// Remove hash labels from template.Labels before comparing
delete(t1Copy.Labels, appsv1.DefaultDeploymentUniqueLabelKey)
delete(t2Copy.Labels, appsv1.DefaultDeploymentUniqueLabelKey)
return apiequality.Semantic.DeepEqual(t1Copy, t2Copy)
}
// annotationsToSkip lists the annotations that should be preserved from the deployment and not
// copied from the replicaset when rolling a deployment back
var annotationsToSkip = map[string]bool{
corev1.LastAppliedConfigAnnotation: true,
deploymentutil.RevisionAnnotation: true,
deploymentutil.RevisionHistoryAnnotation: true,
deploymentutil.DesiredReplicasAnnotation: true,
deploymentutil.MaxReplicasAnnotation: true,
appsv1.DeprecatedRollbackTo: true,
}
// getPatch returns a patch that can be applied to restore a Deployment to a
// previous version. If the returned error is nil the patch is valid.
func getDeploymentPatch(podTemplate *corev1.PodTemplateSpec, annotations map[string]string) (types.PatchType, []byte, error) {
// Create a patch of the Deployment that replaces spec.template
patch, err := json.Marshal([]interface{}{
map[string]interface{}{
"op": "replace",
"path": "/spec/template",
"value": podTemplate,
},
map[string]interface{}{
"op": "replace",
"path": "/metadata/annotations",
"value": annotations,
},
})
return types.JSONPatchType, patch, err
}
func deploymentRevision(deployment *appsv1.Deployment, c kubernetes.Interface, toRevision int64) (revision *appsv1.ReplicaSet, err error) {
_, allOldRSs, newRS, err := deploymentutil.GetAllReplicaSets(deployment, c.AppsV1())
if err != nil {
return nil, fmt.Errorf("failed to retrieve replica sets from deployment %s: %v", deployment.Name, err)
}
allRSs := allOldRSs
if newRS != nil {
allRSs = append(allRSs, newRS)
}
var (
latestReplicaSet *appsv1.ReplicaSet
latestRevision = int64(-1)
previousReplicaSet *appsv1.ReplicaSet
previousRevision = int64(-1)
)
for _, rs := range allRSs {
if v, err := deploymentutil.Revision(rs); err == nil {
if toRevision == 0 {
if latestRevision < v {
// newest one we've seen so far
previousRevision = latestRevision
previousReplicaSet = latestReplicaSet
latestRevision = v
latestReplicaSet = rs
} else if previousRevision < v {
// second newest one we've seen so far
previousRevision = v
previousReplicaSet = rs
}
} else if toRevision == v {
return rs, nil
}
}
}
if toRevision > 0 {
return nil, revisionNotFoundErr(toRevision)
}
if previousReplicaSet == nil {
return nil, fmt.Errorf("no rollout history found for deployment %q", deployment.Name)
}
return previousReplicaSet, nil
}
type DaemonSetRollbacker struct {
c kubernetes.Interface
}
func (r *DaemonSetRollbacker) Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRun bool) (string, error) {
if toRevision < 0 {
return "", revisionNotFoundErr(toRevision)
}
accessor, err := meta.Accessor(obj)
if err != nil {
return "", fmt.Errorf("failed to create accessor for kind %v: %s", obj.GetObjectKind(), err.Error())
}
ds, history, err := daemonSetHistory(r.c.AppsV1(), accessor.GetNamespace(), accessor.GetName())
if err != nil {
return "", err
}
if toRevision == 0 && len(history) <= 1 {
return "", fmt.Errorf("no last revision to roll back to")
}
toHistory := findHistory(toRevision, history)
if toHistory == nil {
return "", revisionNotFoundErr(toRevision)
}
if dryRun {
appliedDS, err := applyDaemonSetHistory(ds, toHistory)
if err != nil {
return "", err
}
return printPodTemplate(&appliedDS.Spec.Template)
}
// Skip if the revision already matches current DaemonSet
done, err := daemonSetMatch(ds, toHistory)
if err != nil {
return "", err
}
if done {
return fmt.Sprintf("%s (current template already matches revision %d)", rollbackSkipped, toRevision), nil
}
// Restore revision
if _, err = r.c.AppsV1().DaemonSets(accessor.GetNamespace()).Patch(accessor.GetName(), types.StrategicMergePatchType, toHistory.Data.Raw); err != nil {
return "", fmt.Errorf("failed restoring revision %d: %v", toRevision, err)
}
return rollbackSuccess, nil
}
// daemonMatch check if the given DaemonSet's template matches the template stored in the given history.
func daemonSetMatch(ds *appsv1.DaemonSet, history *appsv1.ControllerRevision) (bool, error) {
patch, err := getDaemonSetPatch(ds)
if err != nil {
return false, err
}
return bytes.Equal(patch, history.Data.Raw), nil
}
// getPatch returns a strategic merge patch that can be applied to restore a Daemonset to a
// previous version. If the returned error is nil the patch is valid. The current state that we save is just the
// PodSpecTemplate. We can modify this later to encompass more state (or less) and remain compatible with previously
// recorded patches.
func getDaemonSetPatch(ds *appsv1.DaemonSet) ([]byte, error) {
dsBytes, err := json.Marshal(ds)
if err != nil {
return nil, err
}
var raw map[string]interface{}
err = json.Unmarshal(dsBytes, &raw)
if err != nil {
return nil, err
}
objCopy := make(map[string]interface{})
specCopy := make(map[string]interface{})
// Create a patch of the DaemonSet that replaces spec.template
spec := raw["spec"].(map[string]interface{})
template := spec["template"].(map[string]interface{})
specCopy["template"] = template
template["$patch"] = "replace"
objCopy["spec"] = specCopy
patch, err := json.Marshal(objCopy)
return patch, err
}
type StatefulSetRollbacker struct {
c kubernetes.Interface
}
// toRevision is a non-negative integer, with 0 being reserved to indicate rolling back to previous configuration
func (r *StatefulSetRollbacker) Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRun bool) (string, error) {
if toRevision < 0 {
return "", revisionNotFoundErr(toRevision)
}
accessor, err := meta.Accessor(obj)
if err != nil {
return "", fmt.Errorf("failed to create accessor for kind %v: %s", obj.GetObjectKind(), err.Error())
}
sts, history, err := statefulSetHistory(r.c.AppsV1(), accessor.GetNamespace(), accessor.GetName())
if err != nil {
return "", err
}
if toRevision == 0 && len(history) <= 1 {
return "", fmt.Errorf("no last revision to roll back to")
}
toHistory := findHistory(toRevision, history)
if toHistory == nil {
return "", revisionNotFoundErr(toRevision)
}
if dryRun {
appliedSS, err := applyRevision(sts, toHistory)
if err != nil {
return "", err
}
return printPodTemplate(&appliedSS.Spec.Template)
}
// Skip if the revision already matches current StatefulSet
done, err := statefulsetMatch(sts, toHistory)
if err != nil {
return "", err
}
if done {
return fmt.Sprintf("%s (current template already matches revision %d)", rollbackSkipped, toRevision), nil
}
// Restore revision
if _, err = r.c.AppsV1().StatefulSets(sts.Namespace).Patch(sts.Name, types.StrategicMergePatchType, toHistory.Data.Raw); err != nil {
return "", fmt.Errorf("failed restoring revision %d: %v", toRevision, err)
}
return rollbackSuccess, nil
}
var appsCodec = scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion)
// applyRevision returns a new StatefulSet constructed by restoring the state in revision to set. If the returned error
// is nil, the returned StatefulSet is valid.
func applyRevision(set *appsv1.StatefulSet, revision *appsv1.ControllerRevision) (*appsv1.StatefulSet, error) {
clone := set.DeepCopy()
patched, err := strategicpatch.StrategicMergePatch([]byte(runtime.EncodeOrDie(appsCodec, clone)), revision.Data.Raw, clone)
if err != nil {
return nil, err
}
err = json.Unmarshal(patched, clone)
if err != nil {
return nil, err
}
return clone, nil
}
// statefulsetMatch check if the given StatefulSet's template matches the template stored in the given history.
func statefulsetMatch(ss *appsv1.StatefulSet, history *appsv1.ControllerRevision) (bool, error) {
patch, err := getStatefulSetPatch(ss)
if err != nil {
return false, err
}
return bytes.Equal(patch, history.Data.Raw), nil
}
// getStatefulSetPatch returns a strategic merge patch that can be applied to restore a StatefulSet to a
// previous version. If the returned error is nil the patch is valid. The current state that we save is just the
// PodSpecTemplate. We can modify this later to encompass more state (or less) and remain compatible with previously
// recorded patches.
func getStatefulSetPatch(set *appsv1.StatefulSet) ([]byte, error) {
str, err := runtime.Encode(appsCodec, set)
if err != nil {
return nil, err
}
var raw map[string]interface{}
if err := json.Unmarshal([]byte(str), &raw); err != nil {
return nil, err
}
objCopy := make(map[string]interface{})
specCopy := make(map[string]interface{})
spec := raw["spec"].(map[string]interface{})
template := spec["template"].(map[string]interface{})
specCopy["template"] = template
template["$patch"] = "replace"
objCopy["spec"] = specCopy
patch, err := json.Marshal(objCopy)
return patch, err
}
// findHistory returns a controllerrevision of a specific revision from the given controllerrevisions.
// It returns nil if no such controllerrevision exists.
// If toRevision is 0, the last previously used history is returned.
func findHistory(toRevision int64, allHistory []*appsv1.ControllerRevision) *appsv1.ControllerRevision {
if toRevision == 0 && len(allHistory) <= 1 {
return nil
}
// Find the history to rollback to
var toHistory *appsv1.ControllerRevision
if toRevision == 0 {
// If toRevision == 0, find the latest revision (2nd max)
sort.Sort(historiesByRevision(allHistory))
toHistory = allHistory[len(allHistory)-2]
} else {
for _, h := range allHistory {
if h.Revision == toRevision {
// If toRevision != 0, find the history with matching revision
return h
}
}
}
return toHistory
}
// printPodTemplate converts a given pod template into a human-readable string.
func printPodTemplate(specTemplate *corev1.PodTemplateSpec) (string, error) {
podSpec, err := printTemplate(specTemplate)
if err != nil {
return "", err
}
return fmt.Sprintf("will roll back to %s", podSpec), nil
}
func revisionNotFoundErr(r int64) error {
return fmt.Errorf("unable to find specified revision %v in history", r)
}
// TODO: copied from daemon controller, should extract to a library
type historiesByRevision []*appsv1.ControllerRevision
func (h historiesByRevision) Len() int { return len(h) }
func (h historiesByRevision) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h historiesByRevision) Less(i, j int) bool {
return h[i].Revision < h[j].Revision
}

View File

@@ -1,67 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"reflect"
"testing"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/fake"
)
var rollbacktests = map[schema.GroupKind]reflect.Type{
{Group: "apps", Kind: "DaemonSet"}: reflect.TypeOf(&DaemonSetRollbacker{}),
{Group: "apps", Kind: "StatefulSet"}: reflect.TypeOf(&StatefulSetRollbacker{}),
{Group: "apps", Kind: "Deployment"}: reflect.TypeOf(&DeploymentRollbacker{}),
}
func TestRollbackerFor(t *testing.T) {
fakeClientset := &fake.Clientset{}
for kind, expectedType := range rollbacktests {
result, err := RollbackerFor(kind, fakeClientset)
if err != nil {
t.Fatalf("error getting Rollbacker for a %v: %v", kind.String(), err)
}
if reflect.TypeOf(result) != expectedType {
t.Fatalf("unexpected output type (%v was expected but got %v)", expectedType, reflect.TypeOf(result))
}
}
}
func TestGetDeploymentPatch(t *testing.T) {
patchType, patchBytes, err := getDeploymentPatch(&corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{Image: "foo"}}}}, map[string]string{"a": "true"})
if err != nil {
t.Error(err)
}
if patchType != types.JSONPatchType {
t.Errorf("expected strategic merge patch, got %v", patchType)
}
expectedPatch := `[` +
`{"op":"replace","path":"/spec/template","value":{"metadata":{"creationTimestamp":null},"spec":{"containers":[{"name":"","image":"foo","resources":{}}]}}},` +
`{"op":"replace","path":"/metadata/annotations","value":{"a":"true"}}` +
`]`
if string(patchBytes) != expectedPatch {
t.Errorf("expected:\n%s\ngot\n%s", expectedPatch, string(patchBytes))
}
}

View File

@@ -1,37 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/kubernetes"
)
// Returns a Rollbacker for changing the rollback version of the specified RESTMapping type or an error
func rollbacker(restClientGetter genericclioptions.RESTClientGetter, mapping *meta.RESTMapping) (Rollbacker, error) {
clientConfig, err := restClientGetter.ToRESTConfig()
if err != nil {
return nil, err
}
external, err := kubernetes.NewForConfig(clientConfig)
if err != nil {
return nil, err
}
return RollbackerFor(mapping.GroupVersionKind.GroupKind(), external)
}

View File

@@ -1,153 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"fmt"
appsv1 "k8s.io/api/apps/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubectl/pkg/scheme"
deploymentutil "k8s.io/kubectl/pkg/util/deployment"
)
// StatusViewer provides an interface for resources that have rollout status.
type StatusViewer interface {
Status(obj runtime.Unstructured, revision int64) (string, bool, error)
}
// StatusViewerFor returns a StatusViewer for the resource specified by kind.
func StatusViewerFor(kind schema.GroupKind) (StatusViewer, error) {
switch kind {
case extensionsv1beta1.SchemeGroupVersion.WithKind("Deployment").GroupKind(),
appsv1.SchemeGroupVersion.WithKind("Deployment").GroupKind():
return &DeploymentStatusViewer{}, nil
case extensionsv1beta1.SchemeGroupVersion.WithKind("DaemonSet").GroupKind(),
appsv1.SchemeGroupVersion.WithKind("DaemonSet").GroupKind():
return &DaemonSetStatusViewer{}, nil
case appsv1.SchemeGroupVersion.WithKind("StatefulSet").GroupKind():
return &StatefulSetStatusViewer{}, nil
}
return nil, fmt.Errorf("no status viewer has been implemented for %v", kind)
}
// DeploymentStatusViewer implements the StatusViewer interface.
type DeploymentStatusViewer struct{}
// DaemonSetStatusViewer implements the StatusViewer interface.
type DaemonSetStatusViewer struct{}
// StatefulSetStatusViewer implements the StatusViewer interface.
type StatefulSetStatusViewer struct{}
// Status returns a message describing deployment status, and a bool value indicating if the status is considered done.
func (s *DeploymentStatusViewer) Status(obj runtime.Unstructured, revision int64) (string, bool, error) {
deployment := &appsv1.Deployment{}
err := scheme.Scheme.Convert(obj, deployment, nil)
if err != nil {
return "", false, fmt.Errorf("failed to convert %T to %T: %v", obj, deployment, err)
}
if revision > 0 {
deploymentRev, err := deploymentutil.Revision(deployment)
if err != nil {
return "", false, fmt.Errorf("cannot get the revision of deployment %q: %v", deployment.Name, err)
}
if revision != deploymentRev {
return "", false, fmt.Errorf("desired revision (%d) is different from the running revision (%d)", revision, deploymentRev)
}
}
if deployment.Generation <= deployment.Status.ObservedGeneration {
cond := deploymentutil.GetDeploymentCondition(deployment.Status, appsv1.DeploymentProgressing)
if cond != nil && cond.Reason == deploymentutil.TimedOutReason {
return "", false, fmt.Errorf("deployment %q exceeded its progress deadline", deployment.Name)
}
if deployment.Spec.Replicas != nil && deployment.Status.UpdatedReplicas < *deployment.Spec.Replicas {
return fmt.Sprintf("Waiting for deployment %q rollout to finish: %d out of %d new replicas have been updated...\n", deployment.Name, deployment.Status.UpdatedReplicas, *deployment.Spec.Replicas), false, nil
}
if deployment.Status.Replicas > deployment.Status.UpdatedReplicas {
return fmt.Sprintf("Waiting for deployment %q rollout to finish: %d old replicas are pending termination...\n", deployment.Name, deployment.Status.Replicas-deployment.Status.UpdatedReplicas), false, nil
}
if deployment.Status.AvailableReplicas < deployment.Status.UpdatedReplicas {
return fmt.Sprintf("Waiting for deployment %q rollout to finish: %d of %d updated replicas are available...\n", deployment.Name, deployment.Status.AvailableReplicas, deployment.Status.UpdatedReplicas), false, nil
}
return fmt.Sprintf("deployment %q successfully rolled out\n", deployment.Name), true, nil
}
return fmt.Sprintf("Waiting for deployment spec update to be observed...\n"), false, nil
}
// Status returns a message describing daemon set status, and a bool value indicating if the status is considered done.
func (s *DaemonSetStatusViewer) Status(obj runtime.Unstructured, revision int64) (string, bool, error) {
//ignoring revision as DaemonSets does not have history yet
daemon := &appsv1.DaemonSet{}
err := scheme.Scheme.Convert(obj, daemon, nil)
if err != nil {
return "", false, fmt.Errorf("failed to convert %T to %T: %v", obj, daemon, err)
}
if daemon.Spec.UpdateStrategy.Type != appsv1.RollingUpdateDaemonSetStrategyType {
return "", true, fmt.Errorf("rollout status is only available for %s strategy type", appsv1.RollingUpdateStatefulSetStrategyType)
}
if daemon.Generation <= daemon.Status.ObservedGeneration {
if daemon.Status.UpdatedNumberScheduled < daemon.Status.DesiredNumberScheduled {
return fmt.Sprintf("Waiting for daemon set %q rollout to finish: %d out of %d new pods have been updated...\n", daemon.Name, daemon.Status.UpdatedNumberScheduled, daemon.Status.DesiredNumberScheduled), false, nil
}
if daemon.Status.NumberAvailable < daemon.Status.DesiredNumberScheduled {
return fmt.Sprintf("Waiting for daemon set %q rollout to finish: %d of %d updated pods are available...\n", daemon.Name, daemon.Status.NumberAvailable, daemon.Status.DesiredNumberScheduled), false, nil
}
return fmt.Sprintf("daemon set %q successfully rolled out\n", daemon.Name), true, nil
}
return fmt.Sprintf("Waiting for daemon set spec update to be observed...\n"), false, nil
}
// Status returns a message describing statefulset status, and a bool value indicating if the status is considered done.
func (s *StatefulSetStatusViewer) Status(obj runtime.Unstructured, revision int64) (string, bool, error) {
sts := &appsv1.StatefulSet{}
err := scheme.Scheme.Convert(obj, sts, nil)
if err != nil {
return "", false, fmt.Errorf("failed to convert %T to %T: %v", obj, sts, err)
}
if sts.Spec.UpdateStrategy.Type != appsv1.RollingUpdateStatefulSetStrategyType {
return "", true, fmt.Errorf("rollout status is only available for %s strategy type", appsv1.RollingUpdateStatefulSetStrategyType)
}
if sts.Status.ObservedGeneration == 0 || sts.Generation > sts.Status.ObservedGeneration {
return "Waiting for statefulset spec update to be observed...\n", false, nil
}
if sts.Spec.Replicas != nil && sts.Status.ReadyReplicas < *sts.Spec.Replicas {
return fmt.Sprintf("Waiting for %d pods to be ready...\n", *sts.Spec.Replicas-sts.Status.ReadyReplicas), false, nil
}
if sts.Spec.UpdateStrategy.Type == appsv1.RollingUpdateStatefulSetStrategyType && sts.Spec.UpdateStrategy.RollingUpdate != nil {
if sts.Spec.Replicas != nil && sts.Spec.UpdateStrategy.RollingUpdate.Partition != nil {
if sts.Status.UpdatedReplicas < (*sts.Spec.Replicas - *sts.Spec.UpdateStrategy.RollingUpdate.Partition) {
return fmt.Sprintf("Waiting for partitioned roll out to finish: %d out of %d new pods have been updated...\n",
sts.Status.UpdatedReplicas, *sts.Spec.Replicas-*sts.Spec.UpdateStrategy.RollingUpdate.Partition), false, nil
}
}
return fmt.Sprintf("partitioned roll out complete: %d new pods have been updated...\n",
sts.Status.UpdatedReplicas), true, nil
}
if sts.Status.UpdateRevision != sts.Status.CurrentRevision {
return fmt.Sprintf("waiting for statefulset rolling update to complete %d pods at revision %s...\n",
sts.Status.UpdatedReplicas, sts.Status.UpdateRevision), false, nil
}
return fmt.Sprintf("statefulset rolling update complete %d pods at revision %s...\n", sts.Status.CurrentReplicas, sts.Status.CurrentRevision), true, nil
}

View File

@@ -1,469 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"fmt"
"testing"
apps "k8s.io/api/apps/v1"
api "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/kubectl/pkg/scheme"
)
func TestDeploymentStatusViewerStatus(t *testing.T) {
tests := []struct {
name string
generation int64
specReplicas int32
status apps.DeploymentStatus
msg string
done bool
}{
{
name: "test1",
generation: 0,
specReplicas: 1,
status: apps.DeploymentStatus{
ObservedGeneration: 1,
Replicas: 1,
UpdatedReplicas: 0,
AvailableReplicas: 1,
UnavailableReplicas: 0,
},
msg: "Waiting for deployment \"foo\" rollout to finish: 0 out of 1 new replicas have been updated...\n",
done: false,
},
{
name: "test2",
generation: 1,
specReplicas: 1,
status: apps.DeploymentStatus{
ObservedGeneration: 1,
Replicas: 2,
UpdatedReplicas: 1,
AvailableReplicas: 2,
UnavailableReplicas: 0,
},
msg: "Waiting for deployment \"foo\" rollout to finish: 1 old replicas are pending termination...\n",
done: false,
},
{
name: "test3",
generation: 1,
specReplicas: 2,
status: apps.DeploymentStatus{
ObservedGeneration: 1,
Replicas: 2,
UpdatedReplicas: 2,
AvailableReplicas: 1,
UnavailableReplicas: 1,
},
msg: "Waiting for deployment \"foo\" rollout to finish: 1 of 2 updated replicas are available...\n",
done: false,
},
{
name: "test4",
generation: 1,
specReplicas: 2,
status: apps.DeploymentStatus{
ObservedGeneration: 1,
Replicas: 2,
UpdatedReplicas: 2,
AvailableReplicas: 2,
UnavailableReplicas: 0,
},
msg: "deployment \"foo\" successfully rolled out\n",
done: true,
},
{
name: "test5",
generation: 2,
specReplicas: 2,
status: apps.DeploymentStatus{
ObservedGeneration: 1,
Replicas: 2,
UpdatedReplicas: 2,
AvailableReplicas: 2,
UnavailableReplicas: 0,
},
msg: "Waiting for deployment spec update to be observed...\n",
done: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
d := &apps.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: "bar",
Name: "foo",
UID: "8764ae47-9092-11e4-8393-42010af018ff",
Generation: test.generation,
},
Spec: apps.DeploymentSpec{
Replicas: &test.specReplicas,
},
Status: test.status,
}
unstructuredD := &unstructured.Unstructured{}
err := scheme.Scheme.Convert(d, unstructuredD, nil)
if err != nil {
t.Fatal(err)
}
dsv := &DeploymentStatusViewer{}
msg, done, err := dsv.Status(unstructuredD, 0)
if err != nil {
t.Fatalf("DeploymentStatusViewer.Status(): %v", err)
}
if done != test.done || msg != test.msg {
t.Errorf("DeploymentStatusViewer.Status() for deployment with generation %d, %d replicas specified, and status %+v returned %q, %t, want %q, %t",
test.generation,
test.specReplicas,
test.status,
msg,
done,
test.msg,
test.done,
)
}
})
}
}
func TestDaemonSetStatusViewerStatus(t *testing.T) {
tests := []struct {
name string
generation int64
status apps.DaemonSetStatus
msg string
done bool
}{
{
name: "test1",
generation: 0,
status: apps.DaemonSetStatus{
ObservedGeneration: 1,
UpdatedNumberScheduled: 0,
DesiredNumberScheduled: 1,
NumberAvailable: 0,
},
msg: "Waiting for daemon set \"foo\" rollout to finish: 0 out of 1 new pods have been updated...\n",
done: false,
},
{
name: "test2",
generation: 1,
status: apps.DaemonSetStatus{
ObservedGeneration: 1,
UpdatedNumberScheduled: 2,
DesiredNumberScheduled: 2,
NumberAvailable: 1,
},
msg: "Waiting for daemon set \"foo\" rollout to finish: 1 of 2 updated pods are available...\n",
done: false,
},
{
name: "test3",
generation: 1,
status: apps.DaemonSetStatus{
ObservedGeneration: 1,
UpdatedNumberScheduled: 2,
DesiredNumberScheduled: 2,
NumberAvailable: 2,
},
msg: "daemon set \"foo\" successfully rolled out\n",
done: true,
},
{
name: "test4",
generation: 2,
status: apps.DaemonSetStatus{
ObservedGeneration: 1,
UpdatedNumberScheduled: 2,
DesiredNumberScheduled: 2,
NumberAvailable: 2,
},
msg: "Waiting for daemon set spec update to be observed...\n",
done: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
d := &apps.DaemonSet{
ObjectMeta: metav1.ObjectMeta{
Namespace: "bar",
Name: "foo",
UID: "8764ae47-9092-11e4-8393-42010af018ff",
Generation: test.generation,
},
Spec: apps.DaemonSetSpec{
UpdateStrategy: apps.DaemonSetUpdateStrategy{
Type: apps.RollingUpdateDaemonSetStrategyType,
},
},
Status: test.status,
}
unstructuredD := &unstructured.Unstructured{}
err := scheme.Scheme.Convert(d, unstructuredD, nil)
if err != nil {
t.Fatal(err)
}
dsv := &DaemonSetStatusViewer{}
msg, done, err := dsv.Status(unstructuredD, 0)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if done != test.done || msg != test.msg {
t.Errorf("daemon set with generation %d, %d pods specified, and status:\n%+v\nreturned:\n%q, %t\nwant:\n%q, %t",
test.generation,
d.Status.DesiredNumberScheduled,
test.status,
msg,
done,
test.msg,
test.done,
)
}
})
}
}
func TestStatefulSetStatusViewerStatus(t *testing.T) {
tests := []struct {
name string
generation int64
strategy apps.StatefulSetUpdateStrategy
status apps.StatefulSetStatus
msg string
done bool
err bool
}{
{
name: "on delete returns an error",
generation: 1,
strategy: apps.StatefulSetUpdateStrategy{Type: apps.OnDeleteStatefulSetStrategyType},
status: apps.StatefulSetStatus{
ObservedGeneration: 1,
Replicas: 0,
ReadyReplicas: 1,
CurrentReplicas: 0,
UpdatedReplicas: 0,
},
msg: "",
done: true,
err: true,
},
{
name: "unobserved update is not complete",
generation: 2,
strategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
status: apps.StatefulSetStatus{
ObservedGeneration: 1,
Replicas: 3,
ReadyReplicas: 3,
CurrentReplicas: 3,
UpdatedReplicas: 0,
},
msg: "Waiting for statefulset spec update to be observed...\n",
done: false,
err: false,
},
{
name: "if all pods are not ready the update is not complete",
generation: 1,
strategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
status: apps.StatefulSetStatus{
ObservedGeneration: 2,
Replicas: 3,
ReadyReplicas: 2,
CurrentReplicas: 3,
UpdatedReplicas: 0,
},
msg: fmt.Sprintf("Waiting for %d pods to be ready...\n", 1),
done: false,
err: false,
},
{
name: "partition update completes when all replicas above the partition are updated",
generation: 1,
strategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType,
RollingUpdate: func() *apps.RollingUpdateStatefulSetStrategy {
partition := int32(2)
return &apps.RollingUpdateStatefulSetStrategy{Partition: &partition}
}()},
status: apps.StatefulSetStatus{
ObservedGeneration: 2,
Replicas: 3,
ReadyReplicas: 3,
CurrentReplicas: 2,
UpdatedReplicas: 1,
},
msg: fmt.Sprintf("partitioned roll out complete: %d new pods have been updated...\n", 1),
done: true,
err: false,
},
{
name: "partition update is in progress if all pods above the partition have not been updated",
generation: 1,
strategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType,
RollingUpdate: func() *apps.RollingUpdateStatefulSetStrategy {
partition := int32(2)
return &apps.RollingUpdateStatefulSetStrategy{Partition: &partition}
}()},
status: apps.StatefulSetStatus{
ObservedGeneration: 2,
Replicas: 3,
ReadyReplicas: 3,
CurrentReplicas: 3,
UpdatedReplicas: 0,
},
msg: fmt.Sprintf("Waiting for partitioned roll out to finish: %d out of %d new pods have been updated...\n", 0, 1),
done: true,
err: false,
},
{
name: "update completes when all replicas are current",
generation: 1,
strategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
status: apps.StatefulSetStatus{
ObservedGeneration: 2,
Replicas: 3,
ReadyReplicas: 3,
CurrentReplicas: 3,
UpdatedReplicas: 3,
CurrentRevision: "foo",
UpdateRevision: "foo",
},
msg: fmt.Sprintf("statefulset rolling update complete %d pods at revision %s...\n", 3, "foo"),
done: true,
err: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s := newStatefulSet(3)
s.Status = test.status
s.Spec.UpdateStrategy = test.strategy
s.Generation = test.generation
unstructuredS := &unstructured.Unstructured{}
err := scheme.Scheme.Convert(s, unstructuredS, nil)
if err != nil {
t.Fatal(err)
}
dsv := &StatefulSetStatusViewer{}
msg, done, err := dsv.Status(unstructuredS, 0)
if test.err && err == nil {
t.Fatalf("%s: expected error", test.name)
}
if !test.err && err != nil {
t.Fatalf("%s: %s", test.name, err)
}
if done && !test.done {
t.Errorf("%s: want done %v got %v", test.name, done, test.done)
}
if msg != test.msg {
t.Errorf("%s: want message %s got %s", test.name, test.msg, msg)
}
})
}
}
func TestDaemonSetStatusViewerStatusWithWrongUpdateStrategyType(t *testing.T) {
d := &apps.DaemonSet{
ObjectMeta: metav1.ObjectMeta{
Namespace: "bar",
Name: "foo",
UID: "8764ae47-9092-11e4-8393-42010af018ff",
},
Spec: apps.DaemonSetSpec{
UpdateStrategy: apps.DaemonSetUpdateStrategy{
Type: apps.OnDeleteDaemonSetStrategyType,
},
},
}
unstructuredD := &unstructured.Unstructured{}
err := scheme.Scheme.Convert(d, unstructuredD, nil)
if err != nil {
t.Fatal(err)
}
dsv := &DaemonSetStatusViewer{}
msg, done, err := dsv.Status(unstructuredD, 0)
errMsg := "rollout status is only available for RollingUpdate strategy type"
if err == nil || err.Error() != errMsg {
t.Errorf("Status for daemon sets with UpdateStrategy type different than RollingUpdate should return error. Instead got: msg: %s\ndone: %t\n err: %v", msg, done, err)
}
}
func newStatefulSet(replicas int32) *apps.StatefulSet {
return &apps.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: metav1.NamespaceDefault,
Labels: map[string]string{"a": "b"},
},
Spec: apps.StatefulSetSpec{
PodManagementPolicy: apps.OrderedReadyPodManagement,
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}},
Template: api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"a": "b"},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "test",
Image: "test_image",
ImagePullPolicy: api.PullIfNotPresent,
},
},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
},
Replicas: &replicas,
UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
},
Status: apps.StatefulSetStatus{},
}
}

View File

@@ -1,26 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"k8s.io/apimachinery/pkg/api/meta"
)
// statusViewer returns a StatusViewer for printing rollout status.
func statusViewer(mapping *meta.RESTMapping) (StatusViewer, error) {
return StatusViewerFor(mapping.GroupVersionKind.GroupKind())
}

View File

@@ -1,91 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"fmt"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
batchv1 "k8s.io/api/batch/v1"
batchv1beta1 "k8s.io/api/batch/v1beta1"
batchv2alpha1 "k8s.io/api/batch/v2alpha1"
"k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
)
func updatePodSpecForObject(obj runtime.Object, fn func(*v1.PodSpec) error) (bool, error) {
switch t := obj.(type) {
case *v1.Pod:
return true, fn(&t.Spec)
// ReplicationController
case *v1.ReplicationController:
if t.Spec.Template == nil {
t.Spec.Template = &v1.PodTemplateSpec{}
}
return true, fn(&t.Spec.Template.Spec)
// Deployment
case *extensionsv1beta1.Deployment:
return true, fn(&t.Spec.Template.Spec)
case *appsv1beta1.Deployment:
return true, fn(&t.Spec.Template.Spec)
case *appsv1beta2.Deployment:
return true, fn(&t.Spec.Template.Spec)
case *appsv1.Deployment:
return true, fn(&t.Spec.Template.Spec)
// DaemonSet
case *extensionsv1beta1.DaemonSet:
return true, fn(&t.Spec.Template.Spec)
case *appsv1beta2.DaemonSet:
return true, fn(&t.Spec.Template.Spec)
case *appsv1.DaemonSet:
return true, fn(&t.Spec.Template.Spec)
// ReplicaSet
case *extensionsv1beta1.ReplicaSet:
return true, fn(&t.Spec.Template.Spec)
case *appsv1beta2.ReplicaSet:
return true, fn(&t.Spec.Template.Spec)
case *appsv1.ReplicaSet:
return true, fn(&t.Spec.Template.Spec)
// StatefulSet
case *appsv1beta1.StatefulSet:
return true, fn(&t.Spec.Template.Spec)
case *appsv1beta2.StatefulSet:
return true, fn(&t.Spec.Template.Spec)
case *appsv1.StatefulSet:
return true, fn(&t.Spec.Template.Spec)
// Job
case *batchv1.Job:
return true, fn(&t.Spec.Template.Spec)
// CronJob
case *batchv1beta1.CronJob:
return true, fn(&t.Spec.JobTemplate.Spec.Template.Spec)
case *batchv2alpha1.CronJob:
return true, fn(&t.Spec.JobTemplate.Spec.Template.Spec)
default:
return false, fmt.Errorf("the object is not a pod or does not have a pod template: %T", t)
}
}

View File

@@ -1,131 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package polymorphichelpers
import (
"testing"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
batchv1 "k8s.io/api/batch/v1"
batchv1beta1 "k8s.io/api/batch/v1beta1"
batchv2alpha1 "k8s.io/api/batch/v2alpha1"
"k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
)
func TestUpdatePodSpecForObject(t *testing.T) {
tests := []struct {
object runtime.Object
expect bool
expectErr bool
}{
{
object: &v1.Pod{},
expect: true,
},
{
object: &v1.ReplicationController{},
expect: true,
},
{
object: &extensionsv1beta1.Deployment{},
expect: true,
},
{
object: &appsv1beta1.Deployment{},
expect: true,
},
{
object: &appsv1beta2.Deployment{},
expect: true,
},
{
object: &appsv1.Deployment{},
expect: true,
},
{
object: &extensionsv1beta1.DaemonSet{},
expect: true,
}, {
object: &appsv1beta2.DaemonSet{},
expect: true,
},
{
object: &appsv1.DaemonSet{},
expect: true,
},
{
object: &extensionsv1beta1.ReplicaSet{},
expect: true,
},
{
object: &appsv1beta2.ReplicaSet{},
expect: true,
},
{
object: &appsv1.ReplicaSet{},
expect: true,
},
{
object: &appsv1beta1.StatefulSet{},
expect: true,
},
{
object: &appsv1beta2.StatefulSet{},
expect: true,
},
{
object: &appsv1.StatefulSet{},
expect: true,
},
{
object: &batchv1.Job{},
expect: true,
},
{
object: &batchv1beta1.CronJob{},
expect: true,
},
{
object: &batchv2alpha1.CronJob{},
expect: true,
},
{
object: &v1.Node{},
expect: false,
expectErr: true,
},
}
for _, test := range tests {
actual, err := updatePodSpecForObject(test.object, func(*v1.PodSpec) error {
return nil
})
if test.expectErr && err == nil {
t.Error("unexpected non-error")
}
if !test.expectErr && err != nil {
t.Errorf("unexpected error: %v", err)
}
if actual != test.expect {
t.Errorf("expected %v, but got %v", test.expect, actual)
}
}
}