Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
k3s
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
k3s
Commits
edabdac0
Commit
edabdac0
authored
May 18, 2017
by
Janet Kuo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement kubectl rollout history and undo for DaemonSet
parent
dbd1503b
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
322 additions
and
33 deletions
+322
-33
test-cmd-util.sh
hack/make-rules/test-cmd-util.sh
+40
-0
rollingupdate-daemonset-rv2.yaml
hack/testdata/rollingupdate-daemonset-rv2.yaml
+27
-0
BUILD
pkg/controller/daemon/BUILD
+1
-0
update.go
pkg/controller/daemon/update.go
+5
-11
BUILD
pkg/kubectl/BUILD
+3
-0
rollout.go
pkg/kubectl/cmd/rollout/rollout.go
+8
-4
rollout_history.go
pkg/kubectl/cmd/rollout/rollout_history.go
+3
-3
rollout_pause.go
pkg/kubectl/cmd/rollout/rollout_pause.go
+1
-1
rollout_status.go
pkg/kubectl/cmd/rollout/rollout_status.go
+1
-1
rollout_undo.go
pkg/kubectl/cmd/rollout/rollout_undo.go
+3
-3
history.go
pkg/kubectl/history.go
+100
-7
rollback.go
pkg/kubectl/rollback.go
+120
-3
versioned_client.go
pkg/kubectl/versioned_client.go
+10
-0
No files found.
hack/make-rules/test-cmd-util.sh
View file @
edabdac0
...
@@ -41,6 +41,8 @@ IMAGE_NGINX="gcr.io/google-containers/nginx:1.7.9"
...
@@ -41,6 +41,8 @@ IMAGE_NGINX="gcr.io/google-containers/nginx:1.7.9"
IMAGE_DEPLOYMENT_R1
=
"gcr.io/google-containers/nginx:test-cmd"
# deployment-revision1.yaml
IMAGE_DEPLOYMENT_R1
=
"gcr.io/google-containers/nginx:test-cmd"
# deployment-revision1.yaml
IMAGE_DEPLOYMENT_R2
=
"
$IMAGE_NGINX
"
# deployment-revision2.yaml
IMAGE_DEPLOYMENT_R2
=
"
$IMAGE_NGINX
"
# deployment-revision2.yaml
IMAGE_PERL
=
"gcr.io/google-containers/perl"
IMAGE_PERL
=
"gcr.io/google-containers/perl"
IMAGE_DAEMONSET_R1
=
"gcr.io/google-containers/pause:2.0"
IMAGE_DAEMONSET_R2
=
"gcr.io/google-containers/pause:latest"
# Expose kubectl directly for readability
# Expose kubectl directly for readability
PATH
=
"
${
KUBE_OUTPUT_HOSTBIN
}
"
:
$PATH
PATH
=
"
${
KUBE_OUTPUT_HOSTBIN
}
"
:
$PATH
...
@@ -71,6 +73,7 @@ subjectaccessreviews="subjectaccessreviews"
...
@@ -71,6 +73,7 @@ subjectaccessreviews="subjectaccessreviews"
thirdpartyresources
=
"thirdpartyresources"
thirdpartyresources
=
"thirdpartyresources"
customresourcedefinitions
=
"customresourcedefinitions"
customresourcedefinitions
=
"customresourcedefinitions"
daemonsets
=
"daemonsets"
daemonsets
=
"daemonsets"
controllerrevisions
=
"controllerrevisions"
# Stops the running kubectl proxy, if there is one.
# Stops the running kubectl proxy, if there is one.
...
@@ -2868,6 +2871,39 @@ run_daemonset_tests() {
...
@@ -2868,6 +2871,39 @@ run_daemonset_tests() {
kubectl apply
-f
hack/testdata/rollingupdate-daemonset.yaml
"
${
kube_flags
[@]
}
"
kubectl apply
-f
hack/testdata/rollingupdate-daemonset.yaml
"
${
kube_flags
[@]
}
"
# Template Generation should stay 1
# Template Generation should stay 1
kube::test::get_object_assert
'daemonsets bind'
"{{
${
template_generation_field
}
}}"
'1'
kube::test::get_object_assert
'daemonsets bind'
"{{
${
template_generation_field
}
}}"
'1'
# Clean up
kubectl delete
-f
hack/testdata/rollingupdate-daemonset.yaml
"
${
kube_flags
[@]
}
"
}
run_daemonset_history_tests
()
{
kube::log::status
"Testing kubectl(v1:daemonsets, v1:controllerrevisions)"
### Test rolling back a DaemonSet
# Pre-condition: no DaemonSet or its pods exists
kube::test::get_object_assert daemonsets
"{{range.items}}{{
$id_field
}}:{{end}}"
''
# Command
# Create a DaemonSet (revision 1)
kubectl apply
-f
hack/testdata/rollingupdate-daemonset.yaml
"
${
kube_flags
[@]
}
"
# Rollback to revision 1 - should be no-op
kubectl rollout undo daemonset
--to-revision
=
1
"
${
kube_flags
[@]
}
"
kube::test::get_object_assert daemonset
"{{range.items}}{{
$daemonset_image_field
}}:{{end}}"
"
${
IMAGE_DAEMONSET_R1
}
:"
# Update the DaemonSet (revision 2)
kubectl apply
-f
hack/testdata/rollingupdate-daemonset-rv2.yaml
"
${
kube_flags
[@]
}
"
kube::test::wait_object_assert daemonset
"{{range.items}}{{
$daemonset_image_field
}}:{{end}}"
"
${
IMAGE_DAEMONSET_R2
}
:"
# Rollback to revision 1 with dry-run - should be no-op
kubectl rollout undo daemonset
--dry-run
=
true
"
${
kube_flags
[@]
}
"
kube::test::get_object_assert daemonset
"{{range.items}}{{
$daemonset_image_field
}}:{{end}}"
"
${
IMAGE_DAEMONSET_R2
}
:"
# Rollback to revision 1
kubectl rollout undo daemonset
--to-revision
=
1
"
${
kube_flags
[@]
}
"
kube::test::wait_object_assert daemonset
"{{range.items}}{{
$daemonset_image_field
}}:{{end}}"
"
${
IMAGE_DAEMONSET_R1
}
:"
# Rollback to revision 1000000 - should fail
output_message
=
$(
!
kubectl rollout undo daemonset
--to-revision
=
1000000
"
${
kube_flags
[@]
}
"
2>&1
)
kube::test::if_has_string
"
${
output_message
}
"
"unable to find specified revision"
kube::test::get_object_assert daemonset
"{{range.items}}{{
$daemonset_image_field
}}:{{end}}"
"
${
IMAGE_DAEMONSET_R1
}
:"
# Rollback to last revision
kubectl rollout undo daemonset
"
${
kube_flags
[@]
}
"
kube::test::wait_object_assert daemonset
"{{range.items}}{{
$daemonset_image_field
}}:{{end}}"
"
${
IMAGE_DAEMONSET_R2
}
:"
# Clean up
kubectl delete
-f
hack/testdata/rollingupdate-daemonset.yaml
"
${
kube_flags
[@]
}
"
kubectl delete
-f
hack/testdata/rollingupdate-daemonset.yaml
"
${
kube_flags
[@]
}
"
}
}
...
@@ -3103,6 +3139,7 @@ runTests() {
...
@@ -3103,6 +3139,7 @@ runTests() {
pdb_min_available
=
".spec.minAvailable"
pdb_min_available
=
".spec.minAvailable"
pdb_max_unavailable
=
".spec.maxUnavailable"
pdb_max_unavailable
=
".spec.maxUnavailable"
template_generation_field
=
".spec.templateGeneration"
template_generation_field
=
".spec.templateGeneration"
daemonset_image_field
=
"(index .spec.template.spec.containers 0).image"
# Make sure "default" namespace exists.
# Make sure "default" namespace exists.
if
kube::test::if_supports_resource
"
${
namespaces
}
"
;
then
if
kube::test::if_supports_resource
"
${
namespaces
}
"
;
then
...
@@ -3555,6 +3592,9 @@ runTests() {
...
@@ -3555,6 +3592,9 @@ runTests() {
if
kube::test::if_supports_resource
"
${
daemonsets
}
"
;
then
if
kube::test::if_supports_resource
"
${
daemonsets
}
"
;
then
run_daemonset_tests
run_daemonset_tests
if
kube::test::if_supports_resource
"
${
controllerrevisions
}
"
;
then
run_daemonset_history_tests
fi
fi
fi
###########################
###########################
...
...
hack/testdata/rollingupdate-daemonset-rv2.yaml
0 → 100644
View file @
edabdac0
apiVersion
:
extensions/v1beta1
kind
:
DaemonSet
metadata
:
name
:
bind
spec
:
updateStrategy
:
type
:
RollingUpdate
rollingUpdate
:
maxUnavailable
:
10%
template
:
metadata
:
labels
:
service
:
bind
spec
:
affinity
:
podAntiAffinity
:
requiredDuringSchedulingIgnoredDuringExecution
:
-
labelSelector
:
matchExpressions
:
-
key
:
"
service"
operator
:
"
In"
values
:
[
"
bind"
]
topologyKey
:
"
kubernetes.io/hostname"
namespaces
:
[]
containers
:
-
name
:
kubernetes-pause
image
:
gcr.io/google-containers/pause:latest
pkg/controller/daemon/BUILD
View file @
edabdac0
...
@@ -48,6 +48,7 @@ go_library(
...
@@ -48,6 +48,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
...
...
pkg/controller/daemon/update.go
View file @
edabdac0
...
@@ -17,8 +17,6 @@ limitations under the License.
...
@@ -17,8 +17,6 @@ limitations under the License.
package
daemon
package
daemon
import
(
import
(
"bytes"
"encoding/json"
"fmt"
"fmt"
"sort"
"sort"
...
@@ -29,6 +27,7 @@ import (
...
@@ -29,6 +27,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime"
intstrutil
"k8s.io/apimachinery/pkg/util/intstr"
intstrutil
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/api/v1"
podutil
"k8s.io/kubernetes/pkg/api/v1/pod"
podutil
"k8s.io/kubernetes/pkg/api/v1/pod"
...
@@ -297,23 +296,18 @@ func (dsc *DaemonSetsController) controlledHistories(ds *extensions.DaemonSet) (
...
@@ -297,23 +296,18 @@ func (dsc *DaemonSetsController) controlledHistories(ds *extensions.DaemonSet) (
// Match check if ds template is semantically equal to the template stored in history
// Match check if ds template is semantically equal to the template stored in history
func
Match
(
template
*
v1
.
PodTemplateSpec
,
history
*
apps
.
ControllerRevision
)
(
bool
,
error
)
{
func
Match
(
template
*
v1
.
PodTemplateSpec
,
history
*
apps
.
ControllerRevision
)
(
bool
,
error
)
{
t
,
err
:=
d
ecodeHistory
(
history
)
t
,
err
:=
D
ecodeHistory
(
history
)
return
apiequality
.
Semantic
.
DeepEqual
(
template
,
t
),
err
return
apiequality
.
Semantic
.
DeepEqual
(
template
,
t
),
err
}
}
func
decodeHistory
(
history
*
apps
.
ControllerRevision
)
(
*
v1
.
PodTemplateSpec
,
error
)
{
func
DecodeHistory
(
history
*
apps
.
ControllerRevision
)
(
*
v1
.
PodTemplateSpec
,
error
)
{
raw
:=
history
.
Data
.
Raw
decoder
:=
json
.
NewDecoder
(
bytes
.
NewBuffer
(
raw
))
template
:=
v1
.
PodTemplateSpec
{}
template
:=
v1
.
PodTemplateSpec
{}
err
:=
decoder
.
Decode
(
&
template
)
err
:=
json
.
Unmarshal
(
history
.
Data
.
Raw
,
&
template
)
return
&
template
,
err
return
&
template
,
err
}
}
func
encodeTemplate
(
template
*
v1
.
PodTemplateSpec
)
([]
byte
,
error
)
{
func
encodeTemplate
(
template
*
v1
.
PodTemplateSpec
)
([]
byte
,
error
)
{
buffer
:=
new
(
bytes
.
Buffer
)
return
json
.
Marshal
(
template
)
encoder
:=
json
.
NewEncoder
(
buffer
)
err
:=
encoder
.
Encode
(
template
)
return
buffer
.
Bytes
(),
err
}
}
func
(
dsc
*
DaemonSetsController
)
snapshot
(
ds
*
extensions
.
DaemonSet
,
revision
int64
)
(
*
apps
.
ControllerRevision
,
error
)
{
func
(
dsc
*
DaemonSetsController
)
snapshot
(
ds
*
extensions
.
DaemonSet
,
revision
int64
)
(
*
apps
.
ControllerRevision
,
error
)
{
...
...
pkg/kubectl/BUILD
View file @
edabdac0
...
@@ -66,6 +66,7 @@ go_library(
...
@@ -66,6 +66,7 @@ go_library(
"//pkg/apis/policy:go_default_library",
"//pkg/apis/policy:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/client/clientset_generated/clientset:go_default_library",
"//pkg/client/clientset_generated/clientset:go_default_library",
"//pkg/client/clientset_generated/clientset/typed/apps/v1beta1:go_default_library",
"//pkg/client/clientset_generated/clientset/typed/core/v1:go_default_library",
"//pkg/client/clientset_generated/clientset/typed/core/v1:go_default_library",
"//pkg/client/clientset_generated/clientset/typed/extensions/v1beta1:go_default_library",
"//pkg/client/clientset_generated/clientset/typed/extensions/v1beta1:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
...
@@ -76,6 +77,7 @@ go_library(
...
@@ -76,6 +77,7 @@ go_library(
"//pkg/client/retry:go_default_library",
"//pkg/client/retry:go_default_library",
"//pkg/client/unversioned:go_default_library",
"//pkg/client/unversioned:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/controller/daemon:go_default_library",
"//pkg/controller/deployment/util:go_default_library",
"//pkg/controller/deployment/util:go_default_library",
"//pkg/credentialprovider:go_default_library",
"//pkg/credentialprovider:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/kubectl/resource:go_default_library",
...
@@ -88,6 +90,7 @@ go_library(
...
@@ -88,6 +90,7 @@ go_library(
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
...
...
pkg/kubectl/cmd/rollout/rollout.go
View file @
edabdac0
...
@@ -28,15 +28,20 @@ import (
...
@@ -28,15 +28,20 @@ import (
var
(
var
(
rollout_long
=
templates
.
LongDesc
(
`
rollout_long
=
templates
.
LongDesc
(
`
Manage
a deployment using subcommands like "kubectl rollout undo deployment/abc"`
)
Manage
the rollout of a resource.`
+
rollout_valid_resources
)
rollout_example
=
templates
.
Examples
(
`
rollout_example
=
templates
.
Examples
(
`
# Rollback to the previous deployment
# Rollback to the previous deployment
kubectl rollout undo deployment/abc`
)
kubectl rollout undo deployment/abc
# Check the rollout status of a daemonset
kubectl rollout status daemonset/foo`
)
rollout_valid_resources
=
dedent
.
Dedent
(
`
rollout_valid_resources
=
dedent
.
Dedent
(
`
Valid resource types include:
Valid resource types include:
* deployments
* deployments
* daemonsets
`
)
`
)
)
)
...
@@ -44,7 +49,7 @@ func NewCmdRollout(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
...
@@ -44,7 +49,7 @@ func NewCmdRollout(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
cmd
:=
&
cobra
.
Command
{
cmd
:=
&
cobra
.
Command
{
Use
:
"rollout SUBCOMMAND"
,
Use
:
"rollout SUBCOMMAND"
,
Short
:
i18n
.
T
(
"Manage
a deployment rollout
"
),
Short
:
i18n
.
T
(
"Manage
the rollout of a resource
"
),
Long
:
rollout_long
,
Long
:
rollout_long
,
Example
:
rollout_example
,
Example
:
rollout_example
,
Run
:
cmdutil
.
DefaultSubCommandRun
(
errOut
),
Run
:
cmdutil
.
DefaultSubCommandRun
(
errOut
),
...
@@ -54,7 +59,6 @@ func NewCmdRollout(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
...
@@ -54,7 +59,6 @@ func NewCmdRollout(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
cmd
.
AddCommand
(
NewCmdRolloutPause
(
f
,
out
))
cmd
.
AddCommand
(
NewCmdRolloutPause
(
f
,
out
))
cmd
.
AddCommand
(
NewCmdRolloutResume
(
f
,
out
))
cmd
.
AddCommand
(
NewCmdRolloutResume
(
f
,
out
))
cmd
.
AddCommand
(
NewCmdRolloutUndo
(
f
,
out
))
cmd
.
AddCommand
(
NewCmdRolloutUndo
(
f
,
out
))
cmd
.
AddCommand
(
NewCmdRolloutStatus
(
f
,
out
))
cmd
.
AddCommand
(
NewCmdRolloutStatus
(
f
,
out
))
return
cmd
return
cmd
...
...
pkg/kubectl/cmd/rollout/rollout_history.go
View file @
edabdac0
...
@@ -37,14 +37,14 @@ var (
...
@@ -37,14 +37,14 @@ var (
# View the rollout history of a deployment
# View the rollout history of a deployment
kubectl rollout history deployment/abc
kubectl rollout history deployment/abc
# View the details of d
eploymen
t revision 3
# View the details of d
aemonse
t revision 3
kubectl rollout history d
eploymen
t/abc --revision=3`
)
kubectl rollout history d
aemonse
t/abc --revision=3`
)
)
)
func
NewCmdRolloutHistory
(
f
cmdutil
.
Factory
,
out
io
.
Writer
)
*
cobra
.
Command
{
func
NewCmdRolloutHistory
(
f
cmdutil
.
Factory
,
out
io
.
Writer
)
*
cobra
.
Command
{
options
:=
&
resource
.
FilenameOptions
{}
options
:=
&
resource
.
FilenameOptions
{}
validArgs
:=
[]
string
{
"deployment"
}
validArgs
:=
[]
string
{
"deployment"
,
"daemonset"
}
argAliases
:=
kubectl
.
ResourceAliases
(
validArgs
)
argAliases
:=
kubectl
.
ResourceAliases
(
validArgs
)
cmd
:=
&
cobra
.
Command
{
cmd
:=
&
cobra
.
Command
{
...
...
pkg/kubectl/cmd/rollout/rollout_pause.go
View file @
edabdac0
...
@@ -53,7 +53,7 @@ var (
...
@@ -53,7 +53,7 @@ var (
Mark the provided resource as paused
Mark the provided resource as paused
Paused resources will not be reconciled by a controller.
Paused resources will not be reconciled by a controller.
Use
\"kubectl rollout resume\
" to resume a paused resource.
Use
"kubectl rollout resume
" to resume a paused resource.
Currently only deployments support being paused.`
)
Currently only deployments support being paused.`
)
pause_example
=
templates
.
Examples
(
`
pause_example
=
templates
.
Examples
(
`
...
...
pkg/kubectl/cmd/rollout/rollout_status.go
View file @
edabdac0
...
@@ -50,7 +50,7 @@ var (
...
@@ -50,7 +50,7 @@ var (
func
NewCmdRolloutStatus
(
f
cmdutil
.
Factory
,
out
io
.
Writer
)
*
cobra
.
Command
{
func
NewCmdRolloutStatus
(
f
cmdutil
.
Factory
,
out
io
.
Writer
)
*
cobra
.
Command
{
options
:=
&
resource
.
FilenameOptions
{}
options
:=
&
resource
.
FilenameOptions
{}
validArgs
:=
[]
string
{
"deployment"
}
validArgs
:=
[]
string
{
"deployment"
,
"daemonset"
}
argAliases
:=
kubectl
.
ResourceAliases
(
validArgs
)
argAliases
:=
kubectl
.
ResourceAliases
(
validArgs
)
cmd
:=
&
cobra
.
Command
{
cmd
:=
&
cobra
.
Command
{
...
...
pkg/kubectl/cmd/rollout/rollout_undo.go
View file @
edabdac0
...
@@ -54,8 +54,8 @@ var (
...
@@ -54,8 +54,8 @@ var (
# Rollback to the previous deployment
# Rollback to the previous deployment
kubectl rollout undo deployment/abc
kubectl rollout undo deployment/abc
# Rollback to d
eploymen
t revision 3
# Rollback to d
aemonse
t revision 3
kubectl rollout undo d
eploymen
t/abc --to-revision=3
kubectl rollout undo d
aemonse
t/abc --to-revision=3
# Rollback to the previous deployment with dry-run
# Rollback to the previous deployment with dry-run
kubectl rollout undo --dry-run=true deployment/abc`
)
kubectl rollout undo --dry-run=true deployment/abc`
)
...
@@ -64,7 +64,7 @@ var (
...
@@ -64,7 +64,7 @@ var (
func
NewCmdRolloutUndo
(
f
cmdutil
.
Factory
,
out
io
.
Writer
)
*
cobra
.
Command
{
func
NewCmdRolloutUndo
(
f
cmdutil
.
Factory
,
out
io
.
Writer
)
*
cobra
.
Command
{
options
:=
&
UndoOptions
{}
options
:=
&
UndoOptions
{}
validArgs
:=
[]
string
{
"deployment"
}
validArgs
:=
[]
string
{
"deployment"
,
"daemonset"
}
argAliases
:=
kubectl
.
ResourceAliases
(
validArgs
)
argAliases
:=
kubectl
.
ResourceAliases
(
validArgs
)
cmd
:=
&
cobra
.
Command
{
cmd
:=
&
cobra
.
Command
{
...
...
pkg/kubectl/history.go
View file @
edabdac0
...
@@ -29,8 +29,11 @@ import (
...
@@ -29,8 +29,11 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/apps"
appsv1beta1
"k8s.io/kubernetes/pkg/apis/apps/v1beta1"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apis/extensions"
clientset
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
clientset
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/controller/daemon"
deploymentutil
"k8s.io/kubernetes/pkg/controller/deployment/util"
deploymentutil
"k8s.io/kubernetes/pkg/controller/deployment/util"
printersinternal
"k8s.io/kubernetes/pkg/printers/internalversion"
printersinternal
"k8s.io/kubernetes/pkg/printers/internalversion"
sliceutil
"k8s.io/kubernetes/pkg/util/slice"
sliceutil
"k8s.io/kubernetes/pkg/util/slice"
...
@@ -49,6 +52,8 @@ func HistoryViewerFor(kind schema.GroupKind, c clientset.Interface) (HistoryView
...
@@ -49,6 +52,8 @@ func HistoryViewerFor(kind schema.GroupKind, c clientset.Interface) (HistoryView
switch
kind
{
switch
kind
{
case
extensions
.
Kind
(
"Deployment"
),
apps
.
Kind
(
"Deployment"
)
:
case
extensions
.
Kind
(
"Deployment"
),
apps
.
Kind
(
"Deployment"
)
:
return
&
DeploymentHistoryViewer
{
c
},
nil
return
&
DeploymentHistoryViewer
{
c
},
nil
case
extensions
.
Kind
(
"DaemonSet"
)
:
return
&
DaemonSetHistoryViewer
{
c
},
nil
}
}
return
nil
,
fmt
.
Errorf
(
"no history viewer has been implemented for %q"
,
kind
)
return
nil
,
fmt
.
Errorf
(
"no history viewer has been implemented for %q"
,
kind
)
}
}
...
@@ -100,16 +105,80 @@ func (h *DeploymentHistoryViewer) ViewHistory(namespace, name string, revision i
...
@@ -100,16 +105,80 @@ func (h *DeploymentHistoryViewer) ViewHistory(namespace, name string, revision i
if
!
ok
{
if
!
ok
{
return
""
,
fmt
.
Errorf
(
"unable to find the specified revision"
)
return
""
,
fmt
.
Errorf
(
"unable to find the specified revision"
)
}
}
buf
:=
bytes
.
NewBuffer
([]
byte
{})
return
printTemplate
(
template
)
internalTemplate
:=
&
api
.
PodTemplateSpec
{}
}
if
err
:=
v1
.
Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec
(
template
,
internalTemplate
,
nil
);
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"failed to convert podtemplate, %v"
,
err
)
// 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
\t
CHANGE-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
*
v1
.
PodTemplateSpec
)
(
string
,
error
)
{
buf
:=
bytes
.
NewBuffer
([]
byte
{})
internalTemplate
:=
&
api
.
PodTemplateSpec
{}
if
err
:=
v1
.
Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec
(
template
,
internalTemplate
,
nil
);
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"failed to convert podtemplate, %v"
,
err
)
}
w
:=
printersinternal
.
NewPrefixWriter
(
buf
)
printersinternal
.
DescribePodTemplate
(
internalTemplate
,
w
)
return
buf
.
String
(),
nil
}
type
DaemonSetHistoryViewer
struct
{
c
clientset
.
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
,
err
:=
h
.
c
.
Extensions
()
.
DaemonSets
(
namespace
)
.
Get
(
name
,
metav1
.
GetOptions
{})
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"failed to retrieve DaemonSet %s: %v"
,
name
,
err
)
}
allHistory
,
err
:=
controlledHistories
(
h
.
c
,
ds
)
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"unable to find history controlled by DaemonSet %s: %v"
,
ds
.
Name
,
err
)
}
historyInfo
:=
make
(
map
[
int64
]
*
appsv1beta1
.
ControllerRevision
)
for
_
,
history
:=
range
allHistory
{
// 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"
)
}
template
,
err
:=
daemon
.
DecodeHistory
(
history
)
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"unable to decode history %s"
,
history
.
Name
)
}
}
w
:=
printersinternal
.
NewPrefixWriter
(
buf
)
return
printTemplate
(
template
)
printersinternal
.
DescribePodTemplate
(
internalTemplate
,
w
)
return
buf
.
String
(),
nil
}
}
// Print an overview of all Revisions
// Sort the revisionToChangeCause map by revision
// Sort the revisionToChangeCause map by revision
revisions
:=
make
([]
int64
,
0
,
len
(
historyInfo
))
revisions
:=
make
([]
int64
,
0
,
len
(
historyInfo
))
for
r
:=
range
historyInfo
{
for
r
:=
range
historyInfo
{
...
@@ -131,6 +200,30 @@ func (h *DeploymentHistoryViewer) ViewHistory(namespace, name string, revision i
...
@@ -131,6 +200,30 @@ func (h *DeploymentHistoryViewer) ViewHistory(namespace, name string, revision i
})
})
}
}
// controlledHistories returns all ControllerRevisions controlled by the given DaemonSet
// TODO: Use external version DaemonSet instead when #3955 is fixed
func
controlledHistories
(
c
clientset
.
Interface
,
ds
*
extensions
.
DaemonSet
)
([]
*
appsv1beta1
.
ControllerRevision
,
error
)
{
var
result
[]
*
appsv1beta1
.
ControllerRevision
selector
,
err
:=
metav1
.
LabelSelectorAsSelector
(
ds
.
Spec
.
Selector
)
if
err
!=
nil
{
return
nil
,
err
}
versionedClient
:=
versionedClientsetForDaemonSet
(
c
)
historyList
,
err
:=
versionedClient
.
AppsV1beta1
()
.
ControllerRevisions
(
ds
.
Namespace
)
.
List
(
metav1
.
ListOptions
{
LabelSelector
:
selector
.
String
()})
if
err
!=
nil
{
return
nil
,
err
}
for
i
:=
range
historyList
.
Items
{
history
:=
historyList
.
Items
[
i
]
// Skip history that doesn't belong to the DaemonSet
if
controllerRef
:=
controller
.
GetControllerOf
(
&
history
);
controllerRef
==
nil
||
controllerRef
.
UID
!=
ds
.
UID
{
continue
}
result
=
append
(
result
,
&
history
)
}
return
result
,
nil
}
// TODO: copied here until this becomes a describer
// TODO: copied here until this becomes a describer
func
tabbedString
(
f
func
(
io
.
Writer
)
error
)
(
string
,
error
)
{
func
tabbedString
(
f
func
(
io
.
Writer
)
error
)
(
string
,
error
)
{
out
:=
new
(
tabwriter
.
Writer
)
out
:=
new
(
tabwriter
.
Writer
)
...
...
pkg/kubectl/rollback.go
View file @
edabdac0
...
@@ -21,8 +21,10 @@ import (
...
@@ -21,8 +21,10 @@ import (
"fmt"
"fmt"
"os"
"os"
"os/signal"
"os/signal"
"sort"
"syscall"
"syscall"
apiequality
"k8s.io/apimachinery/pkg/api/equality"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/schema"
...
@@ -30,14 +32,22 @@ import (
...
@@ -30,14 +32,22 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/apps"
appsv1beta1
"k8s.io/kubernetes/pkg/apis/apps/v1beta1"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apis/extensions"
externalextensions
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
externalextensions
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
clientset
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
clientset
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/retry"
"k8s.io/kubernetes/pkg/controller/daemon"
deploymentutil
"k8s.io/kubernetes/pkg/controller/deployment/util"
deploymentutil
"k8s.io/kubernetes/pkg/controller/deployment/util"
printersinternal
"k8s.io/kubernetes/pkg/printers/internalversion"
printersinternal
"k8s.io/kubernetes/pkg/printers/internalversion"
sliceutil
"k8s.io/kubernetes/pkg/util/slice"
sliceutil
"k8s.io/kubernetes/pkg/util/slice"
)
)
const
(
rollbackSuccess
=
"rolled back"
rollbackSkipped
=
"skipped rollback"
)
// Rollbacker provides an interface for resources that can be rolled back.
// Rollbacker provides an interface for resources that can be rolled back.
type
Rollbacker
interface
{
type
Rollbacker
interface
{
Rollback
(
obj
runtime
.
Object
,
updatedAnnotations
map
[
string
]
string
,
toRevision
int64
,
dryRun
bool
)
(
string
,
error
)
Rollback
(
obj
runtime
.
Object
,
updatedAnnotations
map
[
string
]
string
,
toRevision
int64
,
dryRun
bool
)
(
string
,
error
)
...
@@ -47,6 +57,8 @@ func RollbackerFor(kind schema.GroupKind, c clientset.Interface) (Rollbacker, er
...
@@ -47,6 +57,8 @@ func RollbackerFor(kind schema.GroupKind, c clientset.Interface) (Rollbacker, er
switch
kind
{
switch
kind
{
case
extensions
.
Kind
(
"Deployment"
),
apps
.
Kind
(
"Deployment"
)
:
case
extensions
.
Kind
(
"Deployment"
),
apps
.
Kind
(
"Deployment"
)
:
return
&
DeploymentRollbacker
{
c
},
nil
return
&
DeploymentRollbacker
{
c
},
nil
case
extensions
.
Kind
(
"DaemonSet"
)
:
return
&
DaemonSetRollbacker
{
c
},
nil
}
}
return
nil
,
fmt
.
Errorf
(
"no rollbacker has been implemented for %q"
,
kind
)
return
nil
,
fmt
.
Errorf
(
"no rollbacker has been implemented for %q"
,
kind
)
}
}
...
@@ -126,9 +138,9 @@ func isRollbackEvent(e *api.Event) (bool, string) {
...
@@ -126,9 +138,9 @@ func isRollbackEvent(e *api.Event) (bool, string) {
for
_
,
reason
:=
range
rollbackEventReasons
{
for
_
,
reason
:=
range
rollbackEventReasons
{
if
e
.
Reason
==
reason
{
if
e
.
Reason
==
reason
{
if
reason
==
deploymentutil
.
RollbackDone
{
if
reason
==
deploymentutil
.
RollbackDone
{
return
true
,
"rolled back"
return
true
,
rollbackSuccess
}
}
return
true
,
fmt
.
Sprintf
(
"
skipped rollback (%s: %s)"
,
e
.
Reason
,
e
.
Message
)
return
true
,
fmt
.
Sprintf
(
"
%s (%s: %s)"
,
rollbackSkipped
,
e
.
Reason
,
e
.
Message
)
}
}
}
}
return
false
,
""
return
false
,
""
...
@@ -165,7 +177,7 @@ func simpleDryRun(deployment *extensions.Deployment, c clientset.Interface, toRe
...
@@ -165,7 +177,7 @@ func simpleDryRun(deployment *extensions.Deployment, c clientset.Interface, toRe
if
toRevision
>
0
{
if
toRevision
>
0
{
template
,
ok
:=
revisionToSpec
[
toRevision
]
template
,
ok
:=
revisionToSpec
[
toRevision
]
if
!
ok
{
if
!
ok
{
return
""
,
fmt
.
Errorf
(
"unable to find specified revision"
)
return
""
,
revisionNotFoundErr
(
toRevision
)
}
}
buf
:=
bytes
.
NewBuffer
([]
byte
{})
buf
:=
bytes
.
NewBuffer
([]
byte
{})
internalTemplate
:=
&
api
.
PodTemplateSpec
{}
internalTemplate
:=
&
api
.
PodTemplateSpec
{}
...
@@ -195,3 +207,108 @@ func simpleDryRun(deployment *extensions.Deployment, c clientset.Interface, toRe
...
@@ -195,3 +207,108 @@ func simpleDryRun(deployment *extensions.Deployment, c clientset.Interface, toRe
printersinternal
.
DescribePodTemplate
(
internalTemplate
,
w
)
printersinternal
.
DescribePodTemplate
(
internalTemplate
,
w
)
return
buf
.
String
(),
nil
return
buf
.
String
(),
nil
}
}
type
DaemonSetRollbacker
struct
{
c
clientset
.
Interface
}
func
(
r
*
DaemonSetRollbacker
)
Rollback
(
obj
runtime
.
Object
,
updatedAnnotations
map
[
string
]
string
,
toRevision
int64
,
dryRun
bool
)
(
string
,
error
)
{
if
toRevision
<
0
{
return
""
,
revisionNotFoundErr
(
toRevision
)
}
ds
,
ok
:=
obj
.
(
*
extensions
.
DaemonSet
)
if
!
ok
{
return
""
,
fmt
.
Errorf
(
"passed object is not a DaemonSet: %#v"
,
obj
)
}
allHistory
,
err
:=
controlledHistories
(
r
.
c
,
ds
)
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"unable to find history controlled by DaemonSet %s: %v"
,
ds
.
Name
,
err
)
}
if
toRevision
==
0
&&
len
(
allHistory
)
<=
1
{
return
""
,
fmt
.
Errorf
(
"no last revision to roll back to"
)
}
// Find the history to rollback to
var
toHistory
*
appsv1beta1
.
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
toHistory
=
h
break
}
}
}
if
toHistory
==
nil
{
return
""
,
revisionNotFoundErr
(
toRevision
)
}
// Get the template of the history to rollback to
toTemplate
,
err
:=
getInternalTemplate
(
toHistory
)
if
err
!=
nil
{
return
""
,
err
}
if
dryRun
{
content
:=
bytes
.
NewBuffer
([]
byte
{})
w
:=
printersinternal
.
NewPrefixWriter
(
content
)
printersinternal
.
DescribePodTemplate
(
toTemplate
,
w
)
return
fmt
.
Sprintf
(
"will roll back to %s"
,
content
.
String
()),
nil
}
// Update DaemonSet template, and retry on conflict
skipUpdate
:=
false
retryErr
:=
retry
.
RetryOnConflict
(
retry
.
DefaultBackoff
,
func
()
error
{
var
err
error
ds
,
err
=
r
.
c
.
Extensions
()
.
DaemonSets
(
ds
.
Namespace
)
.
Get
(
ds
.
Name
,
metav1
.
GetOptions
{})
if
err
!=
nil
{
return
err
}
if
apiequality
.
Semantic
.
DeepEqual
(
toTemplate
,
&
ds
.
Spec
.
Template
)
{
skipUpdate
=
true
return
nil
}
ds
.
Spec
.
Template
=
*
toTemplate
_
,
err
=
r
.
c
.
Extensions
()
.
DaemonSets
(
ds
.
Namespace
)
.
Update
(
ds
)
return
err
})
if
retryErr
!=
nil
{
return
""
,
retryErr
}
if
skipUpdate
{
return
fmt
.
Sprintf
(
"%s (current template already matches revision %d)"
,
rollbackSkipped
,
toRevision
),
nil
}
return
rollbackSuccess
,
nil
}
func
getInternalTemplate
(
toHistory
*
appsv1beta1
.
ControllerRevision
)
(
*
api
.
PodTemplateSpec
,
error
)
{
template
,
err
:=
daemon
.
DecodeHistory
(
toHistory
)
if
err
!=
nil
{
return
nil
,
err
}
internalTemplate
:=
&
api
.
PodTemplateSpec
{}
if
err
:=
v1
.
Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec
(
template
,
internalTemplate
,
nil
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"failed to convert podtemplate, %v"
,
err
)
}
return
internalTemplate
,
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
[]
*
appsv1beta1
.
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
}
pkg/kubectl/versioned_client.go
View file @
edabdac0
...
@@ -18,6 +18,7 @@ package kubectl
...
@@ -18,6 +18,7 @@ package kubectl
import
(
import
(
externalclientset
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
externalclientset
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
apps
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/apps/v1beta1"
core
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/core/v1"
core
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/core/v1"
extensions
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/extensions/v1beta1"
extensions
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/extensions/v1beta1"
internalclientset
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
internalclientset
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
...
@@ -32,3 +33,12 @@ func versionedClientsetForDeployment(internalClient internalclientset.Interface)
...
@@ -32,3 +33,12 @@ func versionedClientsetForDeployment(internalClient internalclientset.Interface)
ExtensionsV1beta1Client
:
extensions
.
New
(
internalClient
.
Extensions
()
.
RESTClient
()),
ExtensionsV1beta1Client
:
extensions
.
New
(
internalClient
.
Extensions
()
.
RESTClient
()),
}
}
}
}
func
versionedClientsetForDaemonSet
(
internalClient
internalclientset
.
Interface
)
externalclientset
.
Interface
{
if
internalClient
==
nil
{
return
&
externalclientset
.
Clientset
{}
}
return
&
externalclientset
.
Clientset
{
AppsV1beta1Client
:
apps
.
New
(
internalClient
.
Apps
()
.
RESTClient
()),
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment