mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-01-08 06:15:40 +00:00
Add cmd to list plugins (#1535)
* Add cmd to list plugins Signed-off-by: Carlisia <carlisiac@vmware.com>
This commit is contained in:
committed by
Nolan Brubaker
parent
bc7ee686d7
commit
0a771e6a53
1
changelogs/unreleased/1535-carlisia
Normal file
1
changelogs/unreleased/1535-carlisia
Normal file
@@ -0,0 +1 @@
|
||||
Add CLI command to list (get) all Velero plugins
|
||||
@@ -16,7 +16,9 @@ limitations under the License.
|
||||
|
||||
package v1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
@@ -45,6 +47,12 @@ const (
|
||||
ServerStatusRequestPhaseProcessed ServerStatusRequestPhase = "Processed"
|
||||
)
|
||||
|
||||
// PluginInfo contains attributes of a Velero plugin
|
||||
type PluginInfo struct {
|
||||
Name string `json:"name"`
|
||||
Kind string `json:"kind"`
|
||||
}
|
||||
|
||||
// ServerStatusRequestStatus is the current status of a ServerStatusRequest.
|
||||
type ServerStatusRequestStatus struct {
|
||||
// Phase is the current lifecycle phase of the ServerStatusRequest.
|
||||
@@ -56,6 +64,9 @@ type ServerStatusRequestStatus struct {
|
||||
|
||||
// ServerVersion is the Velero server version.
|
||||
ServerVersion string `json:"serverVersion"`
|
||||
|
||||
// Plugins list information about the plugins running on the Velero server
|
||||
Plugins []PluginInfo `json:"plugins"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
@@ -616,6 +616,22 @@ func (in *ObjectStorageLocation) DeepCopy() *ObjectStorageLocation {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PluginInfo) DeepCopyInto(out *PluginInfo) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginInfo.
|
||||
func (in *PluginInfo) DeepCopy() *PluginInfo {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PluginInfo)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PodVolumeBackup) DeepCopyInto(out *PodVolumeBackup) {
|
||||
*out = *in
|
||||
@@ -1227,6 +1243,11 @@ func (in *ServerStatusRequestSpec) DeepCopy() *ServerStatusRequestSpec {
|
||||
func (in *ServerStatusRequestStatus) DeepCopyInto(out *ServerStatusRequestStatus) {
|
||||
*out = *in
|
||||
in.ProcessedTimestamp.DeepCopyInto(&out.ProcessedTimestamp)
|
||||
if in.Plugins != nil {
|
||||
in, out := &in.Plugins, &out.Plugins
|
||||
*out = make([]PluginInfo, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/heptio/velero/pkg/client"
|
||||
"github.com/heptio/velero/pkg/cmd/cli/backup"
|
||||
"github.com/heptio/velero/pkg/cmd/cli/backuplocation"
|
||||
"github.com/heptio/velero/pkg/cmd/cli/plugin"
|
||||
"github.com/heptio/velero/pkg/cmd/cli/restore"
|
||||
"github.com/heptio/velero/pkg/cmd/cli/schedule"
|
||||
"github.com/heptio/velero/pkg/cmd/cli/snapshotlocation"
|
||||
@@ -49,12 +50,16 @@ func NewCommand(f client.Factory) *cobra.Command {
|
||||
snapshotLocationCommand := snapshotlocation.NewGetCommand(f, "snapshot-locations")
|
||||
snapshotLocationCommand.Aliases = []string{"snapshot-location"}
|
||||
|
||||
pluginCommand := plugin.NewGetCommand(f, "plugins")
|
||||
pluginCommand.Aliases = []string{"plugin"}
|
||||
|
||||
c.AddCommand(
|
||||
backupCommand,
|
||||
scheduleCommand,
|
||||
restoreCommand,
|
||||
backupLocationCommand,
|
||||
snapshotLocationCommand,
|
||||
pluginCommand,
|
||||
)
|
||||
|
||||
return c
|
||||
|
||||
69
pkg/cmd/cli/plugin/get.go
Normal file
69
pkg/cmd/cli/plugin/get.go
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
Copyright 2019 the Velero 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 plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/heptio/velero/pkg/client"
|
||||
"github.com/heptio/velero/pkg/cmd"
|
||||
"github.com/heptio/velero/pkg/cmd/cli/serverstatus"
|
||||
"github.com/heptio/velero/pkg/cmd/util/output"
|
||||
)
|
||||
|
||||
func NewGetCommand(f client.Factory, use string) *cobra.Command {
|
||||
serverStatusGetter := &serverstatus.DefaultServerStatusGetter{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
c := &cobra.Command{
|
||||
Use: use,
|
||||
Short: "Get information for all plugins on the velero server",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
err := output.ValidateFlags(c)
|
||||
cmd.CheckError(err)
|
||||
|
||||
serverStatusGetter := &serverstatus.DefaultServerStatusGetter{
|
||||
Namespace: f.Namespace(),
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
client, err := f.Client()
|
||||
cmd.CheckError(err)
|
||||
|
||||
veleroClient := client.VeleroV1()
|
||||
|
||||
serverStatus, err := serverStatusGetter.GetServerStatus(veleroClient)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stdout, "<error getting plugin information: %s>\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = output.PrintWithFormat(c, serverStatus)
|
||||
cmd.CheckError(err)
|
||||
},
|
||||
}
|
||||
|
||||
c.Flags().DurationVar(&serverStatusGetter.Timeout, "timeout", serverStatusGetter.Timeout, "maximum time to wait for plugin information to be reported")
|
||||
output.BindFlagsSimple(c.Flags())
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -32,6 +32,7 @@ func NewCommand(f client.Factory) *cobra.Command {
|
||||
c.AddCommand(
|
||||
NewAddCommand(f),
|
||||
NewRemoveCommand(f),
|
||||
NewGetCommand(f, "get"),
|
||||
)
|
||||
|
||||
return c
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2017 the Velero contributors.
|
||||
Copyright 2019 the Velero contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -14,93 +14,37 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package version
|
||||
package serverstatus
|
||||
|
||||
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"
|
||||
|
||||
velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
|
||||
"github.com/heptio/velero/pkg/buildinfo"
|
||||
"github.com/heptio/velero/pkg/client"
|
||||
"github.com/heptio/velero/pkg/cmd"
|
||||
velerov1client "github.com/heptio/velero/pkg/generated/clientset/versioned/typed/velero/v1"
|
||||
"github.com/heptio/velero/pkg/serverstatusrequest"
|
||||
)
|
||||
|
||||
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 velero version and associated image",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
var veleroClient velerov1client.ServerStatusRequestsGetter
|
||||
|
||||
if !clientOnly {
|
||||
client, err := f.Client()
|
||||
cmd.CheckError(err)
|
||||
|
||||
veleroClient = client.VeleroV1()
|
||||
}
|
||||
|
||||
printVersion(os.Stdout, clientOnly, veleroClient, 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 velero client version, not server version")
|
||||
|
||||
return c
|
||||
type ServerStatusGetter interface {
|
||||
GetServerStatus(client velerov1client.ServerStatusRequestsGetter) (*velerov1api.ServerStatusRequest, error)
|
||||
}
|
||||
|
||||
func printVersion(w io.Writer, clientOnly bool, client velerov1client.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 DefaultServerStatusGetter struct {
|
||||
Namespace string
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
type serverStatusGetter interface {
|
||||
getServerStatus(client velerov1client.ServerStatusRequestsGetter) (*velerov1api.ServerStatusRequest, error)
|
||||
}
|
||||
func (g *DefaultServerStatusGetter) GetServerStatus(client velerov1client.ServerStatusRequestsGetter) (*velerov1api.ServerStatusRequest, error) {
|
||||
req := serverstatusrequest.NewBuilder().Namespace(g.Namespace).GenerateName("velero-cli-").ServerStatusRequest()
|
||||
|
||||
type defaultServerStatusGetter struct {
|
||||
namespace string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (g *defaultServerStatusGetter) getServerStatus(client velerov1client.ServerStatusRequestsGetter) (*velerov1api.ServerStatusRequest, error) {
|
||||
req := serverstatusrequest.NewBuilder().Namespace(g.namespace).GenerateName("velero-cli-").Build()
|
||||
|
||||
created, err := client.ServerStatusRequests(g.namespace).Create(req)
|
||||
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)
|
||||
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.
|
||||
@@ -108,13 +52,13 @@ func (g *defaultServerStatusGetter) getServerStatus(client velerov1client.Server
|
||||
//FieldSelector: "metadata.name=" + req.Name
|
||||
ResourceVersion: created.ResourceVersion,
|
||||
}
|
||||
watcher, err := client.ServerStatusRequests(g.namespace).Watch(listOptions)
|
||||
watcher, err := client.ServerStatusRequests(g.Namespace).Watch(listOptions)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
defer watcher.Stop()
|
||||
|
||||
expired := time.NewTimer(g.timeout)
|
||||
expired := time.NewTimer(g.Timeout)
|
||||
defer expired.Stop()
|
||||
|
||||
Loop:
|
||||
81
pkg/cmd/cli/version/version.go
Normal file
81
pkg/cmd/cli/version/version.go
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
Copyright 2017, 2019 the Velero 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 (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/heptio/velero/pkg/buildinfo"
|
||||
"github.com/heptio/velero/pkg/client"
|
||||
"github.com/heptio/velero/pkg/cmd"
|
||||
"github.com/heptio/velero/pkg/cmd/cli/serverstatus"
|
||||
velerov1client "github.com/heptio/velero/pkg/generated/clientset/versioned/typed/velero/v1"
|
||||
)
|
||||
|
||||
func NewCommand(f client.Factory) *cobra.Command {
|
||||
clientOnly := false
|
||||
serverStatusGetter := &serverstatus.DefaultServerStatusGetter{
|
||||
Namespace: f.Namespace(),
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
c := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the velero version and associated image",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
var veleroClient velerov1client.ServerStatusRequestsGetter
|
||||
|
||||
if !clientOnly {
|
||||
client, err := f.Client()
|
||||
cmd.CheckError(err)
|
||||
|
||||
veleroClient = client.VeleroV1()
|
||||
}
|
||||
|
||||
printVersion(os.Stdout, clientOnly, veleroClient, 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 velero client version, not server version")
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func printVersion(w io.Writer, clientOnly bool, client velerov1client.ServerStatusRequestsGetter, serverStatusGetter serverstatus.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)
|
||||
}
|
||||
@@ -73,7 +73,7 @@ func TestPrintVersion(t *testing.T) {
|
||||
{
|
||||
name: "server status getter returns normally",
|
||||
clientOnly: false,
|
||||
serverStatusRequest: serverstatusrequest.NewBuilder().ServerVersion("v1.0.1").Build(),
|
||||
serverStatusRequest: serverstatusrequest.NewBuilder().ServerVersion("v1.0.1").ServerStatusRequest(),
|
||||
getterError: nil,
|
||||
want: clientVersion + "Server:\n\tVersion: v1.0.1\n",
|
||||
},
|
||||
@@ -88,9 +88,9 @@ func TestPrintVersion(t *testing.T) {
|
||||
)
|
||||
defer serverStatusGetter.AssertExpectations(t)
|
||||
|
||||
// getServerStatus should only be called when clientOnly = false
|
||||
// GetServerStatus should only be called when clientOnly = false
|
||||
if !tc.clientOnly {
|
||||
serverStatusGetter.On("getServerStatus", client.VeleroV1()).Return(tc.serverStatusRequest, tc.getterError)
|
||||
serverStatusGetter.On("GetServerStatus", client.VeleroV1()).Return(tc.serverStatusRequest, tc.getterError)
|
||||
}
|
||||
|
||||
printVersion(buf, tc.clientOnly, client.VeleroV1(), serverStatusGetter)
|
||||
@@ -105,8 +105,8 @@ type mockServerStatusGetter struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// getServerStatus provides a mock function with given fields: client
|
||||
func (_m *mockServerStatusGetter) getServerStatus(client v1.ServerStatusRequestsGetter) (*velerov1.ServerStatusRequest, error) {
|
||||
// GetServerStatus provides a mock function with given fields: client
|
||||
func (_m *mockServerStatusGetter) GetServerStatus(client v1.ServerStatusRequestsGetter) (*velerov1.ServerStatusRequest, error) {
|
||||
ret := _m.Called(client)
|
||||
|
||||
var r0 *velerov1.ServerStatusRequest
|
||||
@@ -734,6 +734,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
||||
s.logger,
|
||||
s.veleroClient.VeleroV1(),
|
||||
s.sharedInformerFactory.Velero().V1().ServerStatusRequests(),
|
||||
s.pluginRegistry,
|
||||
)
|
||||
|
||||
return controllerRunInfo{
|
||||
|
||||
@@ -43,6 +43,11 @@ func BindFlags(flags *pflag.FlagSet) {
|
||||
flags.Bool("show-labels", false, "show labels in the last column")
|
||||
}
|
||||
|
||||
// BindFlagsSimple defines the output format flag only.
|
||||
func BindFlagsSimple(flags *pflag.FlagSet) {
|
||||
flags.StringP("output", "o", "table", "Output display format. For create commands, display the object but do not send it to the server. Valid formats are 'table', 'json', and 'yaml'. 'table' is not valid for the install command.")
|
||||
}
|
||||
|
||||
// ClearOutputFlagDefault sets the current and default value
|
||||
// of the "output" flag to the empty string.
|
||||
func ClearOutputFlagDefault(cmd *cobra.Command) {
|
||||
@@ -153,6 +158,7 @@ func printTable(cmd *cobra.Command, obj runtime.Object) (bool, error) {
|
||||
printer.Handler(backupStorageLocationColumns, nil, printBackupStorageLocationList)
|
||||
printer.Handler(volumeSnapshotLocationColumns, nil, printVolumeSnapshotLocation)
|
||||
printer.Handler(volumeSnapshotLocationColumns, nil, printVolumeSnapshotLocationList)
|
||||
printer.Handler(pluginColumns, nil, printPluginList)
|
||||
|
||||
err = printer.PrintObj(obj, os.Stdout)
|
||||
if err != nil {
|
||||
|
||||
62
pkg/cmd/util/output/plugin_printer.go
Normal file
62
pkg/cmd/util/output/plugin_printer.go
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2019 the Velero 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 output
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
|
||||
velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
pluginColumns = []string{"NAME", "KIND"}
|
||||
)
|
||||
|
||||
func printPluginList(list *velerov1api.ServerStatusRequest, w io.Writer, options printers.PrintOptions) error {
|
||||
plugins := list.Status.Plugins
|
||||
sortByKindAndName(plugins)
|
||||
|
||||
for _, plugin := range plugins {
|
||||
if err := printPlugin(plugin, w, options); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sortByKindAndName(plugins []velerov1api.PluginInfo) {
|
||||
sort.Slice(plugins, func(i, j int) bool {
|
||||
if plugins[i].Kind != plugins[j].Kind {
|
||||
return plugins[i].Kind < plugins[j].Kind
|
||||
}
|
||||
return plugins[i].Name < plugins[j].Name
|
||||
})
|
||||
}
|
||||
|
||||
func printPlugin(plugin velerov1api.PluginInfo, w io.Writer, options printers.PrintOptions) error {
|
||||
name := printers.FormatResourceName(options.Kind, plugin.Name, options.WithKind)
|
||||
|
||||
if _, err := fmt.Fprintf(w, "%s\t%s\n", name, plugin.Kind); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -37,9 +37,9 @@ import (
|
||||
"github.com/heptio/velero/pkg/cmd/cli/restore"
|
||||
"github.com/heptio/velero/pkg/cmd/cli/schedule"
|
||||
"github.com/heptio/velero/pkg/cmd/cli/snapshotlocation"
|
||||
"github.com/heptio/velero/pkg/cmd/cli/version"
|
||||
"github.com/heptio/velero/pkg/cmd/server"
|
||||
runplugin "github.com/heptio/velero/pkg/cmd/server/plugin"
|
||||
"github.com/heptio/velero/pkg/cmd/version"
|
||||
)
|
||||
|
||||
func NewCommand(name string) *cobra.Command {
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
velerov1client "github.com/heptio/velero/pkg/generated/clientset/versioned/typed/velero/v1"
|
||||
velerov1informers "github.com/heptio/velero/pkg/generated/informers/externalversions/velero/v1"
|
||||
velerov1listers "github.com/heptio/velero/pkg/generated/listers/velero/v1"
|
||||
"github.com/heptio/velero/pkg/plugin/clientmgmt"
|
||||
"github.com/heptio/velero/pkg/serverstatusrequest"
|
||||
kubeutil "github.com/heptio/velero/pkg/util/kube"
|
||||
)
|
||||
@@ -39,20 +40,23 @@ const statusRequestResyncPeriod = 5 * time.Minute
|
||||
type statusRequestController struct {
|
||||
*genericController
|
||||
|
||||
client velerov1client.ServerStatusRequestsGetter
|
||||
lister velerov1listers.ServerStatusRequestLister
|
||||
clock clock.Clock
|
||||
client velerov1client.ServerStatusRequestsGetter
|
||||
lister velerov1listers.ServerStatusRequestLister
|
||||
pluginRegistry clientmgmt.Registry
|
||||
clock clock.Clock
|
||||
}
|
||||
|
||||
func NewServerStatusRequestController(
|
||||
logger logrus.FieldLogger,
|
||||
client velerov1client.ServerStatusRequestsGetter,
|
||||
informer velerov1informers.ServerStatusRequestInformer,
|
||||
pluginRegistry clientmgmt.Registry,
|
||||
) *statusRequestController {
|
||||
c := &statusRequestController{
|
||||
genericController: newGenericController("serverstatusrequest", logger),
|
||||
client: client,
|
||||
lister: informer.Lister(),
|
||||
pluginRegistry: pluginRegistry,
|
||||
|
||||
clock: clock.RealClock{},
|
||||
}
|
||||
@@ -102,7 +106,7 @@ func (c *statusRequestController) processItem(key string) error {
|
||||
return errors.Wrap(err, "error getting ServerStatusRequest")
|
||||
}
|
||||
|
||||
return serverstatusrequest.Process(req.DeepCopy(), c.client, c.clock, log)
|
||||
return serverstatusrequest.Process(req.DeepCopy(), c.client, c.pluginRegistry, c.clock, log)
|
||||
}
|
||||
|
||||
func (c *statusRequestController) enqueueAllItems() {
|
||||
|
||||
@@ -16,10 +16,6 @@ limitations under the License.
|
||||
|
||||
package framework
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// PluginKind is a type alias for a string that describes
|
||||
// the kind of a Velero-supported plugin.
|
||||
type PluginKind string
|
||||
@@ -46,11 +42,13 @@ const (
|
||||
PluginKindPluginLister PluginKind = "PluginLister"
|
||||
)
|
||||
|
||||
// allPluginKinds contains all the valid plugin kinds that Velero supports, excluding PluginLister because that is not a
|
||||
// AllPluginKinds contains all the valid plugin kinds that Velero supports, excluding PluginLister because that is not a
|
||||
// kind that a developer would ever need to implement (it's handled by Velero and the Velero plugin library code).
|
||||
var allPluginKinds = sets.NewString(
|
||||
PluginKindObjectStore.String(),
|
||||
PluginKindVolumeSnapshotter.String(),
|
||||
PluginKindBackupItemAction.String(),
|
||||
PluginKindRestoreItemAction.String(),
|
||||
)
|
||||
func AllPluginKinds() map[string]PluginKind {
|
||||
allPluginKinds := make(map[string]PluginKind)
|
||||
allPluginKinds[PluginKindObjectStore.String()] = PluginKindObjectStore
|
||||
allPluginKinds[PluginKindVolumeSnapshotter.String()] = PluginKindVolumeSnapshotter
|
||||
allPluginKinds[PluginKindBackupItemAction.String()] = PluginKindBackupItemAction
|
||||
allPluginKinds[PluginKindRestoreItemAction.String()] = PluginKindRestoreItemAction
|
||||
return allPluginKinds
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
Copyright 2018, 2019 the Velero 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 framework
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
func TestAllPluginKinds(t *testing.T) {
|
||||
expected := sets.NewString(
|
||||
PluginKindObjectStore.String(),
|
||||
PluginKindVolumeSnapshotter.String(),
|
||||
PluginKindBackupItemAction.String(),
|
||||
PluginKindRestoreItemAction.String(),
|
||||
)
|
||||
|
||||
assert.True(t, expected.Equal(allPluginKinds))
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func (c *PluginListerGRPCClient) ListPlugins() ([]PluginIdentifier, error) {
|
||||
|
||||
ret := make([]PluginIdentifier, len(resp.Plugins))
|
||||
for i, id := range resp.Plugins {
|
||||
if !allPluginKinds.Has(id.Kind) {
|
||||
if _, ok := AllPluginKinds()[id.Kind]; !ok {
|
||||
return nil, errors.Errorf("invalid plugin kind: %s", id.Kind)
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ func (s *PluginListerGRPCServer) ListPlugins(ctx context.Context, req *proto.Emp
|
||||
|
||||
plugins := make([]*proto.PluginIdentifier, len(list))
|
||||
for i, id := range list {
|
||||
if !allPluginKinds.Has(id.Kind.String()) {
|
||||
if _, ok := AllPluginKinds()[id.Kind.String()]; !ok {
|
||||
return nil, errors.Errorf("invalid plugin kind: %s", id.Kind)
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ type Builder struct {
|
||||
serverStatusRequest velerov1api.ServerStatusRequest
|
||||
}
|
||||
|
||||
// NewBuilder returns a Builder for a ServerStatusRequest.
|
||||
func NewBuilder() *Builder {
|
||||
return &Builder{
|
||||
serverStatusRequest: velerov1api.ServerStatusRequest{
|
||||
@@ -39,7 +40,8 @@ func NewBuilder() *Builder {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) Build() *velerov1api.ServerStatusRequest {
|
||||
// ServerStatusRequest returns the built ServerStatusRequest API object.
|
||||
func (b *Builder) ServerStatusRequest() *velerov1api.ServerStatusRequest {
|
||||
return &b.serverStatusRequest
|
||||
}
|
||||
|
||||
@@ -72,3 +74,8 @@ func (b *Builder) ServerVersion(version string) *Builder {
|
||||
b.serverStatusRequest.Status.ServerVersion = version
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Builder) Plugins(plugins []velerov1api.PluginInfo) *Builder {
|
||||
b.serverStatusRequest.Status.Plugins = plugins
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -29,13 +29,19 @@ import (
|
||||
velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
|
||||
"github.com/heptio/velero/pkg/buildinfo"
|
||||
velerov1client "github.com/heptio/velero/pkg/generated/clientset/versioned/typed/velero/v1"
|
||||
"github.com/heptio/velero/pkg/plugin/framework"
|
||||
)
|
||||
|
||||
const ttl = time.Minute
|
||||
|
||||
type PluginLister interface {
|
||||
// List returns all PluginIdentifiers for kind.
|
||||
List(kind framework.PluginKind) []framework.PluginIdentifier
|
||||
}
|
||||
|
||||
// Process fills out new ServerStatusRequest objects and deletes processed ones
|
||||
// that have expired.
|
||||
func Process(req *velerov1api.ServerStatusRequest, client velerov1client.ServerStatusRequestsGetter, clock clock.Clock, log logrus.FieldLogger) error {
|
||||
func Process(req *velerov1api.ServerStatusRequest, client velerov1client.ServerStatusRequestsGetter, pluginLister PluginLister, clock clock.Clock, log logrus.FieldLogger) error {
|
||||
switch req.Status.Phase {
|
||||
case "", velerov1api.ServerStatusRequestPhaseNew:
|
||||
log.Info("Processing new ServerStatusRequest")
|
||||
@@ -43,6 +49,7 @@ func Process(req *velerov1api.ServerStatusRequest, client velerov1client.ServerS
|
||||
req.Status.ServerVersion = buildinfo.Version
|
||||
req.Status.ProcessedTimestamp.Time = clock.Now()
|
||||
req.Status.Phase = velerov1api.ServerStatusRequestPhaseProcessed
|
||||
req.Status.Plugins = plugins(pluginLister)
|
||||
}))
|
||||
case velerov1api.ServerStatusRequestPhaseProcessed:
|
||||
log.Debug("Checking whether ServerStatusRequest has expired")
|
||||
@@ -88,3 +95,18 @@ func patch(client velerov1client.ServerStatusRequestsGetter, req *velerov1api.Se
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func plugins(pluginLister PluginLister) []velerov1api.PluginInfo {
|
||||
var plugins []velerov1api.PluginInfo
|
||||
for _, v := range framework.AllPluginKinds() {
|
||||
list := pluginLister.List(v)
|
||||
for _, plugin := range list {
|
||||
pluginInfo := velerov1api.PluginInfo{
|
||||
Name: plugin.Name,
|
||||
Kind: plugin.Kind.String(),
|
||||
}
|
||||
plugins = append(plugins, pluginInfo)
|
||||
}
|
||||
}
|
||||
return plugins
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
|
||||
"github.com/heptio/velero/pkg/buildinfo"
|
||||
"github.com/heptio/velero/pkg/generated/clientset/versioned/fake"
|
||||
"github.com/heptio/velero/pkg/plugin/framework"
|
||||
)
|
||||
|
||||
func statusRequestBuilder() *Builder {
|
||||
@@ -46,37 +47,82 @@ func TestProcess(t *testing.T) {
|
||||
buildinfo.Version = "test-version-val"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
req *velerov1api.ServerStatusRequest
|
||||
expected *velerov1api.ServerStatusRequest
|
||||
expectedErrMsg string
|
||||
name string
|
||||
req *velerov1api.ServerStatusRequest
|
||||
reqPluginLister *fakePluginLister
|
||||
expected *velerov1api.ServerStatusRequest
|
||||
expectedErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "server status request with empty phase gets processed",
|
||||
req: statusRequestBuilder().Build(),
|
||||
req: statusRequestBuilder().ServerStatusRequest(),
|
||||
reqPluginLister: &fakePluginLister{
|
||||
plugins: []framework.PluginIdentifier{
|
||||
{
|
||||
Name: "custom.io/myown",
|
||||
Kind: "VolumeSnapshotter",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: statusRequestBuilder().
|
||||
ServerVersion(buildinfo.Version).
|
||||
Phase(velerov1api.ServerStatusRequestPhaseProcessed).
|
||||
ProcessedTimestamp(now).
|
||||
Build(),
|
||||
Plugins([]velerov1api.PluginInfo{
|
||||
{
|
||||
Name: "custom.io/myown",
|
||||
Kind: "VolumeSnapshotter",
|
||||
},
|
||||
}).
|
||||
ServerStatusRequest(),
|
||||
},
|
||||
{
|
||||
name: "server status request with phase=New gets processed",
|
||||
req: statusRequestBuilder().
|
||||
Phase(velerov1api.ServerStatusRequestPhaseNew).
|
||||
Build(),
|
||||
ServerStatusRequest(),
|
||||
reqPluginLister: &fakePluginLister{
|
||||
plugins: []framework.PluginIdentifier{
|
||||
{
|
||||
Name: "velero.io/aws",
|
||||
Kind: "ObjectStore",
|
||||
},
|
||||
{
|
||||
Name: "custom.io/myown",
|
||||
Kind: "VolumeSnapshotter",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: statusRequestBuilder().
|
||||
ServerVersion(buildinfo.Version).
|
||||
Phase(velerov1api.ServerStatusRequestPhaseProcessed).
|
||||
ProcessedTimestamp(now).
|
||||
Build(),
|
||||
Plugins([]velerov1api.PluginInfo{
|
||||
{
|
||||
Name: "velero.io/aws",
|
||||
Kind: "ObjectStore",
|
||||
},
|
||||
{
|
||||
Name: "custom.io/myown",
|
||||
Kind: "VolumeSnapshotter",
|
||||
},
|
||||
}).
|
||||
ServerStatusRequest(),
|
||||
},
|
||||
{
|
||||
name: "server status request with phase=Processed gets deleted if expired",
|
||||
req: statusRequestBuilder().
|
||||
Phase(velerov1api.ServerStatusRequestPhaseProcessed).
|
||||
ProcessedTimestamp(now.Add(-61 * time.Second)).
|
||||
Build(),
|
||||
ServerStatusRequest(),
|
||||
reqPluginLister: &fakePluginLister{
|
||||
plugins: []framework.PluginIdentifier{
|
||||
{
|
||||
Name: "custom.io/myown",
|
||||
Kind: "VolumeSnapshotter",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
@@ -84,20 +130,20 @@ func TestProcess(t *testing.T) {
|
||||
req: statusRequestBuilder().
|
||||
Phase(velerov1api.ServerStatusRequestPhaseProcessed).
|
||||
ProcessedTimestamp(now.Add(-59 * time.Second)).
|
||||
Build(),
|
||||
ServerStatusRequest(),
|
||||
expected: statusRequestBuilder().
|
||||
Phase(velerov1api.ServerStatusRequestPhaseProcessed).
|
||||
ProcessedTimestamp(now.Add(-59 * time.Second)).
|
||||
Build(),
|
||||
ServerStatusRequest(),
|
||||
},
|
||||
{
|
||||
name: "server status request with invalid phase returns an error",
|
||||
req: statusRequestBuilder().
|
||||
Phase(velerov1api.ServerStatusRequestPhase("an-invalid-phase")).
|
||||
Build(),
|
||||
ServerStatusRequest(),
|
||||
expected: statusRequestBuilder().
|
||||
Phase(velerov1api.ServerStatusRequestPhase("an-invalid-phase")).
|
||||
Build(),
|
||||
ServerStatusRequest(),
|
||||
expectedErrMsg: "unexpected ServerStatusRequest phase \"an-invalid-phase\"",
|
||||
},
|
||||
}
|
||||
@@ -106,7 +152,7 @@ func TestProcess(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
client := fake.NewSimpleClientset(tc.req)
|
||||
|
||||
err := Process(tc.req, client.VeleroV1(), clock.NewFakeClock(now), logrus.StandardLogger())
|
||||
err := Process(tc.req, client.VeleroV1(), tc.reqPluginLister, clock.NewFakeClock(now), logrus.StandardLogger())
|
||||
if tc.expectedErrMsg == "" {
|
||||
assert.Nil(t, err)
|
||||
} else {
|
||||
@@ -124,3 +170,18 @@ func TestProcess(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type fakePluginLister struct {
|
||||
plugins []framework.PluginIdentifier
|
||||
}
|
||||
|
||||
func (l *fakePluginLister) List(kind framework.PluginKind) []framework.PluginIdentifier {
|
||||
var plugins []framework.PluginIdentifier
|
||||
for _, plugin := range l.plugins {
|
||||
if plugin.Kind == kind {
|
||||
plugins = append(plugins, plugin)
|
||||
}
|
||||
}
|
||||
|
||||
return plugins
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user