Move pkg/kubectl/polymorphichelpers staging
This commit is contained in:
@@ -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"],
|
||||
|
@@ -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",
|
||||
|
@@ -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
|
||||
|
@@ -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",
|
||||
],
|
||||
)
|
||||
|
@@ -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 (
|
||||
|
@@ -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 {
|
||||
|
@@ -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",
|
||||
|
@@ -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 (
|
||||
|
@@ -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",
|
||||
|
@@ -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 (
|
||||
|
@@ -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",
|
||||
|
@@ -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 (
|
||||
|
@@ -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",
|
||||
|
@@ -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 (
|
||||
|
@@ -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",
|
||||
|
@@ -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.
|
||||
|
@@ -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",
|
||||
|
@@ -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 (
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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 (
|
||||
|
@@ -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"
|
||||
|
@@ -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",
|
||||
|
@@ -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"
|
||||
)
|
||||
|
||||
|
@@ -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",
|
||||
|
@@ -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 (
|
||||
|
@@ -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
|
||||
|
@@ -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 (
|
||||
|
@@ -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 (
|
||||
|
@@ -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"],
|
||||
)
|
@@ -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
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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,
|
||||
}
|
||||
}
|
@@ -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]
|
||||
}
|
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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
|
@@ -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)
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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, ",")
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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")
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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")
|
||||
}
|
||||
}
|
@@ -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")
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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))
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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
|
||||
|
||||
}
|
@@ -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{},
|
||||
}
|
||||
}
|
@@ -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())
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user