show server version in ark version output using ServerStatusRequest CRD

Signed-off-by: Steve Kriss <steve@heptio.com>
This commit is contained in:
Steve Kriss
2018-12-05 10:04:57 -07:00
committed by Steve Kriss
parent 5847dcabba
commit 8a58b217be
23 changed files with 1368 additions and 5 deletions

View File

@@ -62,7 +62,7 @@ operations can also be performed as 'ark backup get' and 'ark schedule create'.`
schedule.NewCommand(f),
restore.NewCommand(f),
server.NewCommand(),
version.NewCommand(),
version.NewCommand(f),
get.NewCommand(f),
describe.NewCommand(f),
create.NewCommand(f),

View File

@@ -681,6 +681,17 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
wg.Done()
}()
serverStatusRequestController := controller.NewServerStatusRequestController(
s.logger,
s.arkClient.ArkV1(),
s.sharedInformerFactory.Ark().V1().ServerStatusRequests(),
)
wg.Add(1)
go func() {
serverStatusRequestController.Run(ctx, 1)
wg.Done()
}()
// SHARED INFORMERS HAVE TO BE STARTED AFTER ALL CONTROLLERS
go s.sharedInformerFactory.Start(ctx.Done())

View File

@@ -18,21 +18,133 @@ package version
import (
"fmt"
"io"
"os"
"time"
"github.com/pkg/errors"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
arkv1api "github.com/heptio/ark/pkg/apis/ark/v1"
"github.com/heptio/ark/pkg/buildinfo"
"github.com/heptio/ark/pkg/client"
"github.com/heptio/ark/pkg/cmd"
arkv1client "github.com/heptio/ark/pkg/generated/clientset/versioned/typed/ark/v1"
"github.com/heptio/ark/pkg/serverstatusrequest"
)
func NewCommand() *cobra.Command {
func NewCommand(f client.Factory) *cobra.Command {
clientOnly := false
serverStatusGetter := &defaultServerStatusGetter{
namespace: f.Namespace(),
timeout: 5 * time.Second,
}
c := &cobra.Command{
Use: "version",
Short: "Print the ark version and associated image",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Version: %s\n", buildinfo.Version)
fmt.Printf("Git commit: %s\n", buildinfo.FormattedGitSHA())
Run: func(c *cobra.Command, args []string) {
var arkClient arkv1client.ServerStatusRequestsGetter
if !clientOnly {
client, err := f.Client()
cmd.CheckError(err)
arkClient = client.ArkV1()
}
printVersion(os.Stdout, clientOnly, arkClient, serverStatusGetter)
},
}
c.Flags().DurationVar(&serverStatusGetter.timeout, "timeout", serverStatusGetter.timeout, "maximum time to wait for server version to be reported")
c.Flags().BoolVar(&clientOnly, "client-only", clientOnly, "only get ark client version, not server version")
return c
}
func printVersion(w io.Writer, clientOnly bool, client arkv1client.ServerStatusRequestsGetter, serverStatusGetter serverStatusGetter) {
fmt.Fprintln(w, "Client:")
fmt.Fprintf(w, "\tVersion: %s\n", buildinfo.Version)
fmt.Fprintf(w, "\tGit commit: %s\n", buildinfo.FormattedGitSHA())
if clientOnly {
return
}
serverStatus, err := serverStatusGetter.getServerStatus(client)
if err != nil {
fmt.Fprintf(w, "<error getting server version: %s>\n", err)
return
}
fmt.Fprintln(w, "Server:")
fmt.Fprintf(w, "\tVersion: %s\n", serverStatus.Status.ServerVersion)
}
type serverStatusGetter interface {
getServerStatus(client arkv1client.ServerStatusRequestsGetter) (*arkv1api.ServerStatusRequest, error)
}
type defaultServerStatusGetter struct {
namespace string
timeout time.Duration
}
func (g *defaultServerStatusGetter) getServerStatus(client arkv1client.ServerStatusRequestsGetter) (*arkv1api.ServerStatusRequest, error) {
req := serverstatusrequest.NewBuilder().Namespace(g.namespace).GenerateName("ark-cli-").Build()
created, err := client.ServerStatusRequests(g.namespace).Create(req)
if err != nil {
return nil, errors.WithStack(err)
}
defer client.ServerStatusRequests(g.namespace).Delete(created.Name, nil)
listOptions := metav1.ListOptions{
// TODO: once the minimum supported Kubernetes version is v1.9.0, uncomment the following line.
// See http://issue.k8s.io/51046 for details.
//FieldSelector: "metadata.name=" + req.Name
ResourceVersion: created.ResourceVersion,
}
watcher, err := client.ServerStatusRequests(g.namespace).Watch(listOptions)
if err != nil {
return nil, errors.WithStack(err)
}
defer watcher.Stop()
expired := time.NewTimer(g.timeout)
defer expired.Stop()
Loop:
for {
select {
case <-expired.C:
return nil, errors.New("timed out waiting for server status request to be processed")
case e := <-watcher.ResultChan():
updated, ok := e.Object.(*arkv1api.ServerStatusRequest)
if !ok {
return nil, errors.Errorf("unexpected type %T", e.Object)
}
// TODO: once the minimum supported Kubernetes version is v1.9.0, remove the following check.
// See http://issue.k8s.io/51046 for details.
if updated.Name != created.Name {
continue
}
switch e.Type {
case watch.Deleted:
return nil, errors.New("server status request was unexpectedly deleted")
case watch.Modified:
if updated.Status.Phase == arkv1api.ServerStatusRequestPhaseProcessed {
req = updated
break Loop
}
}
}
}
return req, nil
}

View File

@@ -0,0 +1,129 @@
/*
Copyright 2019 the Heptio Ark contributors.
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 version
import (
"bytes"
"fmt"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
arkv1 "github.com/heptio/ark/pkg/apis/ark/v1"
"github.com/heptio/ark/pkg/buildinfo"
"github.com/heptio/ark/pkg/generated/clientset/versioned/fake"
v1 "github.com/heptio/ark/pkg/generated/clientset/versioned/typed/ark/v1"
"github.com/heptio/ark/pkg/serverstatusrequest"
)
func TestPrintVersion(t *testing.T) {
// set up some non-empty buildinfo values, but put them back to their
// defaults at the end of the test
var (
origVersion = buildinfo.Version
origGitSHA = buildinfo.GitSHA
origGitTreeState = buildinfo.GitTreeState
)
defer func() {
buildinfo.Version = origVersion
buildinfo.GitSHA = origGitSHA
buildinfo.GitTreeState = origGitTreeState
}()
buildinfo.Version = "v1.0.0"
buildinfo.GitSHA = "somegitsha"
buildinfo.GitTreeState = "dirty"
clientVersion := fmt.Sprintf("Client:\n\tVersion: %s\n\tGit commit: %s\n", buildinfo.Version, buildinfo.FormattedGitSHA())
tests := []struct {
name string
clientOnly bool
serverStatusRequest *arkv1.ServerStatusRequest
getterError error
want string
}{
{
name: "client-only",
clientOnly: true,
want: clientVersion,
},
{
name: "server status getter error",
clientOnly: false,
serverStatusRequest: nil,
getterError: errors.New("an error"),
want: clientVersion + "<error getting server version: an error>\n",
},
{
name: "server status getter returns normally",
clientOnly: false,
serverStatusRequest: serverstatusrequest.NewBuilder().ServerVersion("v1.0.1").Build(),
getterError: nil,
want: clientVersion + "Server:\n\tVersion: v1.0.1\n",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var (
serverStatusGetter = new(mockServerStatusGetter)
buf = new(bytes.Buffer)
client = fake.NewSimpleClientset()
)
defer serverStatusGetter.AssertExpectations(t)
// getServerStatus should only be called when clientOnly = false
if !tc.clientOnly {
serverStatusGetter.On("getServerStatus", client.ArkV1()).Return(tc.serverStatusRequest, tc.getterError)
}
printVersion(buf, tc.clientOnly, client.ArkV1(), serverStatusGetter)
assert.Equal(t, tc.want, buf.String())
})
}
}
// serverStatusGetter is an autogenerated mock type for the serverStatusGetter type
type mockServerStatusGetter struct {
mock.Mock
}
// getServerStatus provides a mock function with given fields: client
func (_m *mockServerStatusGetter) getServerStatus(client v1.ServerStatusRequestsGetter) (*arkv1.ServerStatusRequest, error) {
ret := _m.Called(client)
var r0 *arkv1.ServerStatusRequest
if rf, ok := ret.Get(0).(func(v1.ServerStatusRequestsGetter) *arkv1.ServerStatusRequest); ok {
r0 = rf(client)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*arkv1.ServerStatusRequest)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(v1.ServerStatusRequestsGetter) error); ok {
r1 = rf(client)
} else {
r1 = ret.Error(1)
}
return r0, r1
}