mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-09-04 15:16:58 +00:00
Initial commit
Signed-off-by: Andy Goldstein <andy.goldstein@gmail.com>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 ark
|
||||
|
||||
import (
|
||||
"flag"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
"github.com/heptio/ark/pkg/cmd/cli/backup"
|
||||
"github.com/heptio/ark/pkg/cmd/cli/restore"
|
||||
"github.com/heptio/ark/pkg/cmd/cli/schedule"
|
||||
"github.com/heptio/ark/pkg/cmd/server"
|
||||
"github.com/heptio/ark/pkg/cmd/version"
|
||||
)
|
||||
|
||||
func NewCommand(name string) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: name,
|
||||
Short: "Back up and restore Kubernetes cluster resources.",
|
||||
Long: `Heptio Ark is a tool for managing disaster recovery, specifically for
|
||||
Kubernetes cluster resources. It provides a simple, configurable,
|
||||
and operationally robust way to back up your application state and
|
||||
associated data.`,
|
||||
}
|
||||
|
||||
f := client.NewFactory()
|
||||
f.BindFlags(c.PersistentFlags())
|
||||
|
||||
c.AddCommand(
|
||||
backup.NewCommand(f),
|
||||
schedule.NewCommand(f),
|
||||
restore.NewCommand(f),
|
||||
server.NewCommand(),
|
||||
version.NewCommand(),
|
||||
)
|
||||
|
||||
// add the glog flags
|
||||
c.PersistentFlags().AddGoFlagSet(flag.CommandLine)
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 backup
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
)
|
||||
|
||||
func NewCommand(f client.Factory) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "backup",
|
||||
Short: "Work with backups",
|
||||
Long: "Work with backups",
|
||||
}
|
||||
|
||||
c.AddCommand(
|
||||
NewCreateCommand(f),
|
||||
NewGetCommand(f),
|
||||
|
||||
// Will implement describe later
|
||||
// NewDescribeCommand(f),
|
||||
|
||||
// If you delete a backup and it still exists in object storage, the backup sync controller will
|
||||
// recreate it. Until we have a good UX around this, we're disabling the delete command.
|
||||
// NewDeleteCommand(f),
|
||||
)
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 backup
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
"github.com/heptio/ark/pkg/cmd"
|
||||
"github.com/heptio/ark/pkg/cmd/util/flag"
|
||||
"github.com/heptio/ark/pkg/cmd/util/output"
|
||||
)
|
||||
|
||||
func NewCreateCommand(f client.Factory) *cobra.Command {
|
||||
o := NewCreateOptions()
|
||||
|
||||
c := &cobra.Command{
|
||||
Use: "create NAME",
|
||||
Short: "Create a backup",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
cmd.CheckError(o.Validate(c, args))
|
||||
cmd.CheckError(o.Complete(args))
|
||||
cmd.CheckError(o.Run(c, f))
|
||||
},
|
||||
}
|
||||
|
||||
o.BindFlags(c.Flags())
|
||||
output.BindFlags(c.Flags())
|
||||
output.ClearOutputFlagDefault(c)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
type CreateOptions struct {
|
||||
Name string
|
||||
TTL time.Duration
|
||||
SnapshotVolumes bool
|
||||
IncludeNamespaces flag.StringArray
|
||||
ExcludeNamespaces flag.StringArray
|
||||
IncludeResources flag.StringArray
|
||||
ExcludeResources flag.StringArray
|
||||
Labels flag.Map
|
||||
Selector flag.LabelSelector
|
||||
}
|
||||
|
||||
func NewCreateOptions() *CreateOptions {
|
||||
return &CreateOptions{
|
||||
TTL: 24 * time.Hour,
|
||||
IncludeNamespaces: flag.NewStringArray("*"),
|
||||
Labels: flag.NewMap(),
|
||||
}
|
||||
}
|
||||
|
||||
func (o *CreateOptions) BindFlags(flags *pflag.FlagSet) {
|
||||
flags.DurationVar(&o.TTL, "ttl", o.TTL, "how long before the backup can be garbage collected")
|
||||
flags.BoolVar(&o.SnapshotVolumes, "snapshot-volumes", o.SnapshotVolumes, "take snapshots of PersistentVolumes as part of the backup")
|
||||
flags.Var(&o.IncludeNamespaces, "include-namespaces", "namespaces to include in the backup (use '*' for all namespaces)")
|
||||
flags.Var(&o.ExcludeNamespaces, "exclude-namespaces", "namespaces to exclude from the backup")
|
||||
flags.Var(&o.IncludeResources, "include-resources", "resources to include in the backup, formatted as resource.group, such as storageclasses.storage.k8s.io (use '*' for all resources)")
|
||||
flags.Var(&o.ExcludeResources, "exclude-resources", "resources to exclude from the backup, formatted as resource.group, such as storageclasses.storage.k8s.io")
|
||||
flags.Var(&o.Labels, "labels", "labels to apply to the backup")
|
||||
flags.VarP(&o.Selector, "selector", "l", "only back up resources matching this label selector")
|
||||
}
|
||||
|
||||
func (o *CreateOptions) Validate(c *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return errors.New("you must specify only one argument, the backup's name")
|
||||
}
|
||||
|
||||
if err := output.ValidateFlags(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CreateOptions) Complete(args []string) error {
|
||||
o.Name = args[0]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
arkClient, err := f.Client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
backup := &api.Backup{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: api.DefaultNamespace,
|
||||
Name: o.Name,
|
||||
Labels: o.Labels.Data(),
|
||||
},
|
||||
Spec: api.BackupSpec{
|
||||
IncludedNamespaces: o.IncludeNamespaces,
|
||||
ExcludedNamespaces: o.ExcludeNamespaces,
|
||||
IncludedResources: o.IncludeResources,
|
||||
ExcludedResources: o.ExcludeResources,
|
||||
LabelSelector: o.Selector.LabelSelector,
|
||||
SnapshotVolumes: o.SnapshotVolumes,
|
||||
TTL: metav1.Duration{Duration: o.TTL},
|
||||
},
|
||||
}
|
||||
|
||||
if printed, err := output.PrintWithFormat(c, backup); printed || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = arkClient.ArkV1().Backups(backup.Namespace).Create(backup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Backup %q created successfully.\n", backup.Name)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 backup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
"github.com/heptio/ark/pkg/cmd"
|
||||
)
|
||||
|
||||
func NewDeleteCommand(f client.Factory) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "delete NAME",
|
||||
Short: "Delete a backup",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) != 1 {
|
||||
c.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
arkClient, err := f.Client()
|
||||
cmd.CheckError(err)
|
||||
|
||||
backupName := args[0]
|
||||
|
||||
err = arkClient.ArkV1().Backups(api.DefaultNamespace).Delete(backupName, nil)
|
||||
cmd.CheckError(err)
|
||||
|
||||
fmt.Printf("Backup %q deleted\n", backupName)
|
||||
},
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 backup
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
)
|
||||
|
||||
func NewDescribeCommand(f client.Factory) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "describe",
|
||||
Short: "Describe a backup",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
},
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 backup
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
"github.com/heptio/ark/pkg/cmd"
|
||||
"github.com/heptio/ark/pkg/cmd/util/output"
|
||||
)
|
||||
|
||||
func NewGetCommand(f client.Factory) *cobra.Command {
|
||||
var listOptions metav1.ListOptions
|
||||
|
||||
c := &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "Get backups",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
err := output.ValidateFlags(c)
|
||||
cmd.CheckError(err)
|
||||
|
||||
arkClient, err := f.Client()
|
||||
cmd.CheckError(err)
|
||||
|
||||
var backups *api.BackupList
|
||||
if len(args) > 0 {
|
||||
backups = new(api.BackupList)
|
||||
for _, name := range args {
|
||||
backup, err := arkClient.Ark().Backups(api.DefaultNamespace).Get(name, metav1.GetOptions{})
|
||||
cmd.CheckError(err)
|
||||
backups.Items = append(backups.Items, *backup)
|
||||
}
|
||||
} else {
|
||||
backups, err = arkClient.ArkV1().Backups(api.DefaultNamespace).List(metav1.ListOptions{})
|
||||
cmd.CheckError(err)
|
||||
}
|
||||
|
||||
_, err = output.PrintWithFormat(c, backups)
|
||||
cmd.CheckError(err)
|
||||
},
|
||||
}
|
||||
|
||||
c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "only show items matching this label selector")
|
||||
|
||||
output.BindFlags(c.Flags())
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 config
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
)
|
||||
|
||||
func NewCommand(f client.Factory) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Work with config",
|
||||
Long: "Work with config",
|
||||
}
|
||||
|
||||
c.AddCommand(
|
||||
NewGetCommand(f),
|
||||
NewSetCommand(f),
|
||||
)
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 config
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
)
|
||||
|
||||
func NewGetCommand(f client.Factory) *cobra.Command {
|
||||
c := &cobra.Command{}
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 config
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
)
|
||||
|
||||
func NewSetCommand(f client.Factory) *cobra.Command {
|
||||
c := &cobra.Command{}
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
"github.com/heptio/ark/pkg/cmd"
|
||||
"github.com/heptio/ark/pkg/cmd/util/flag"
|
||||
"github.com/heptio/ark/pkg/cmd/util/output"
|
||||
)
|
||||
|
||||
func NewCreateCommand(f client.Factory) *cobra.Command {
|
||||
o := NewCreateOptions()
|
||||
|
||||
c := &cobra.Command{
|
||||
Use: "create BACKUP",
|
||||
Short: "Create a restore",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
cmd.CheckError(o.Validate(c, args))
|
||||
cmd.CheckError(o.Complete(args))
|
||||
cmd.CheckError(o.Run(c, f))
|
||||
},
|
||||
}
|
||||
|
||||
o.BindFlags(c.Flags())
|
||||
output.BindFlags(c.Flags())
|
||||
output.ClearOutputFlagDefault(c)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
type CreateOptions struct {
|
||||
BackupName string
|
||||
RestoreVolumes bool
|
||||
Labels flag.Map
|
||||
Namespaces flag.StringArray
|
||||
NamespaceMappings flag.Map
|
||||
Selector flag.LabelSelector
|
||||
}
|
||||
|
||||
func NewCreateOptions() *CreateOptions {
|
||||
return &CreateOptions{
|
||||
Labels: flag.NewMap(),
|
||||
NamespaceMappings: flag.NewMap().WithEntryDelimiter(",").WithKeyValueDelimiter(":"),
|
||||
}
|
||||
}
|
||||
|
||||
func (o *CreateOptions) BindFlags(flags *pflag.FlagSet) {
|
||||
flags.BoolVar(&o.RestoreVolumes, "restore-volumes", o.RestoreVolumes, "whether to restore volumes from snapshots")
|
||||
flags.Var(&o.Labels, "labels", "labels to apply to the restore")
|
||||
flags.Var(&o.Namespaces, "namespaces", "comma-separated list of namespaces to restore")
|
||||
flags.Var(&o.NamespaceMappings, "namespace-mappings", "namespace mappings from name in the backup to desired restored name in the form src1:dst1,src2:dst2,...")
|
||||
flags.VarP(&o.Selector, "selector", "l", "only restore resources matching this label selector")
|
||||
}
|
||||
|
||||
func (o *CreateOptions) Validate(c *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return errors.New("you must specify only one argument, the backup's name")
|
||||
}
|
||||
|
||||
if err := output.ValidateFlags(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CreateOptions) Complete(args []string) error {
|
||||
o.BackupName = args[0]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
arkClient, err := f.Client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
restore := &api.Restore{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: api.DefaultNamespace,
|
||||
Name: fmt.Sprintf("%s-%s", o.BackupName, time.Now().Format("20060102150405")),
|
||||
Labels: o.Labels.Data(),
|
||||
},
|
||||
Spec: api.RestoreSpec{
|
||||
BackupName: o.BackupName,
|
||||
Namespaces: o.Namespaces,
|
||||
NamespaceMapping: o.NamespaceMappings.Data(),
|
||||
LabelSelector: o.Selector.LabelSelector,
|
||||
RestorePVs: o.RestoreVolumes,
|
||||
},
|
||||
}
|
||||
|
||||
if printed, err := output.PrintWithFormat(c, restore); printed || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
restore, err = arkClient.ArkV1().Restores(restore.Namespace).Create(restore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Restore %q created successfully.\n", restore.Name)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
"github.com/heptio/ark/pkg/cmd"
|
||||
)
|
||||
|
||||
func NewDeleteCommand(f client.Factory) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "delete NAME",
|
||||
Short: "Delete a restore",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) != 1 {
|
||||
c.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
arkClient, err := f.Client()
|
||||
cmd.CheckError(err)
|
||||
|
||||
name := args[0]
|
||||
|
||||
err = arkClient.ArkV1().Restores(api.DefaultNamespace).Delete(name, nil)
|
||||
cmd.CheckError(err)
|
||||
|
||||
fmt.Printf("Restore %q deleted\n", name)
|
||||
},
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restore
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
)
|
||||
|
||||
func NewDescribeCommand(f client.Factory) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "describe",
|
||||
Short: "Describe a backup",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
},
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restore
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
"github.com/heptio/ark/pkg/cmd"
|
||||
"github.com/heptio/ark/pkg/cmd/util/output"
|
||||
)
|
||||
|
||||
func NewGetCommand(f client.Factory) *cobra.Command {
|
||||
var listOptions metav1.ListOptions
|
||||
|
||||
c := &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "get restores",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
err := output.ValidateFlags(c)
|
||||
cmd.CheckError(err)
|
||||
|
||||
arkClient, err := f.Client()
|
||||
cmd.CheckError(err)
|
||||
|
||||
var restores *api.RestoreList
|
||||
if len(args) > 0 {
|
||||
restores = new(api.RestoreList)
|
||||
for _, name := range args {
|
||||
restore, err := arkClient.Ark().Restores(api.DefaultNamespace).Get(name, metav1.GetOptions{})
|
||||
cmd.CheckError(err)
|
||||
restores.Items = append(restores.Items, *restore)
|
||||
}
|
||||
} else {
|
||||
restores, err = arkClient.ArkV1().Restores(api.DefaultNamespace).List(metav1.ListOptions{})
|
||||
cmd.CheckError(err)
|
||||
}
|
||||
|
||||
if printed, err := output.PrintWithFormat(c, restores); printed || err != nil {
|
||||
cmd.CheckError(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = output.PrintWithFormat(c, restores)
|
||||
cmd.CheckError(err)
|
||||
},
|
||||
}
|
||||
|
||||
c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "only show items matching this label selector")
|
||||
|
||||
output.BindFlags(c.Flags())
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restore
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
)
|
||||
|
||||
func NewCommand(f client.Factory) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "restore",
|
||||
Short: "Work with restores",
|
||||
Long: "Work with restores",
|
||||
}
|
||||
|
||||
c.AddCommand(
|
||||
NewCreateCommand(f),
|
||||
NewGetCommand(f),
|
||||
// Will implement later
|
||||
// NewDescribeCommand(f),
|
||||
NewDeleteCommand(f),
|
||||
)
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 schedule
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
"github.com/heptio/ark/pkg/cmd"
|
||||
"github.com/heptio/ark/pkg/cmd/cli/backup"
|
||||
"github.com/heptio/ark/pkg/cmd/util/output"
|
||||
)
|
||||
|
||||
func NewCreateCommand(f client.Factory) *cobra.Command {
|
||||
o := NewCreateOptions()
|
||||
|
||||
c := &cobra.Command{
|
||||
Use: "create NAME",
|
||||
Short: "Create a schedule",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
cmd.CheckError(o.Validate(c, args))
|
||||
cmd.CheckError(o.Complete(args))
|
||||
cmd.CheckError(o.Run(c, f))
|
||||
},
|
||||
}
|
||||
|
||||
o.BindFlags(c.Flags())
|
||||
output.BindFlags(c.Flags())
|
||||
output.ClearOutputFlagDefault(c)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
type CreateOptions struct {
|
||||
BackupOptions *backup.CreateOptions
|
||||
Schedule string
|
||||
|
||||
labelSelector *metav1.LabelSelector
|
||||
}
|
||||
|
||||
func NewCreateOptions() *CreateOptions {
|
||||
return &CreateOptions{
|
||||
BackupOptions: backup.NewCreateOptions(),
|
||||
}
|
||||
}
|
||||
|
||||
func (o *CreateOptions) BindFlags(flags *pflag.FlagSet) {
|
||||
o.BackupOptions.BindFlags(flags)
|
||||
flags.StringVar(&o.Schedule, "schedule", o.Schedule, "a cron expression specifying a recurring schedule for this backup to run")
|
||||
}
|
||||
|
||||
func (o *CreateOptions) Validate(c *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return errors.New("you must specify only one argument, the schedule's name")
|
||||
}
|
||||
if len(o.Schedule) == 0 {
|
||||
return errors.New("--schedule is required")
|
||||
}
|
||||
|
||||
return o.BackupOptions.Validate(c, args)
|
||||
}
|
||||
|
||||
func (o *CreateOptions) Complete(args []string) error {
|
||||
return o.BackupOptions.Complete(args)
|
||||
}
|
||||
|
||||
func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
arkClient, err := f.Client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
schedule := &api.Schedule{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: api.DefaultNamespace,
|
||||
Name: o.BackupOptions.Name,
|
||||
},
|
||||
Spec: api.ScheduleSpec{
|
||||
Template: api.BackupSpec{
|
||||
IncludedNamespaces: o.BackupOptions.IncludeNamespaces,
|
||||
ExcludedNamespaces: o.BackupOptions.ExcludeNamespaces,
|
||||
IncludedResources: o.BackupOptions.IncludeResources,
|
||||
ExcludedResources: o.BackupOptions.ExcludeResources,
|
||||
LabelSelector: o.BackupOptions.Selector.LabelSelector,
|
||||
SnapshotVolumes: o.BackupOptions.SnapshotVolumes,
|
||||
TTL: metav1.Duration{Duration: o.BackupOptions.TTL},
|
||||
},
|
||||
Schedule: o.Schedule,
|
||||
},
|
||||
}
|
||||
|
||||
if printed, err := output.PrintWithFormat(c, schedule); printed || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = arkClient.ArkV1().Schedules(schedule.Namespace).Create(schedule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Schedule %q created successfully.\n", schedule.Name)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 schedule
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
"github.com/heptio/ark/pkg/cmd"
|
||||
)
|
||||
|
||||
func NewDeleteCommand(f client.Factory) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "delete NAME",
|
||||
Short: "Delete a schedule",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) != 1 {
|
||||
c.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
arkClient, err := f.Client()
|
||||
cmd.CheckError(err)
|
||||
|
||||
name := args[0]
|
||||
|
||||
err = arkClient.ArkV1().Schedules(api.DefaultNamespace).Delete(name, nil)
|
||||
cmd.CheckError(err)
|
||||
|
||||
fmt.Printf("Schedule %q deleted\n", name)
|
||||
},
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 schedule
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
)
|
||||
|
||||
func NewDescribeCommand(f client.Factory) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "describe",
|
||||
Short: "Describe a backup",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
},
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 schedule
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
"github.com/heptio/ark/pkg/cmd"
|
||||
"github.com/heptio/ark/pkg/cmd/util/output"
|
||||
)
|
||||
|
||||
func NewGetCommand(f client.Factory) *cobra.Command {
|
||||
var listOptions metav1.ListOptions
|
||||
|
||||
c := &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "Get schedules",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
err := output.ValidateFlags(c)
|
||||
cmd.CheckError(err)
|
||||
|
||||
arkClient, err := f.Client()
|
||||
cmd.CheckError(err)
|
||||
|
||||
var schedules *api.ScheduleList
|
||||
if len(args) > 0 {
|
||||
schedules = new(api.ScheduleList)
|
||||
for _, name := range args {
|
||||
schedule, err := arkClient.Ark().Schedules(api.DefaultNamespace).Get(name, metav1.GetOptions{})
|
||||
cmd.CheckError(err)
|
||||
schedules.Items = append(schedules.Items, *schedule)
|
||||
}
|
||||
} else {
|
||||
schedules, err = arkClient.ArkV1().Schedules(api.DefaultNamespace).List(metav1.ListOptions{})
|
||||
cmd.CheckError(err)
|
||||
}
|
||||
|
||||
if printed, err := output.PrintWithFormat(c, schedules); printed || err != nil {
|
||||
cmd.CheckError(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = output.PrintWithFormat(c, schedules)
|
||||
cmd.CheckError(err)
|
||||
},
|
||||
}
|
||||
|
||||
c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "only show items matching this label selector")
|
||||
|
||||
output.BindFlags(c.Flags())
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 schedule
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
)
|
||||
|
||||
func NewCommand(f client.Factory) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "schedule",
|
||||
Short: "Work with schedules",
|
||||
Long: "Work with schedules",
|
||||
}
|
||||
|
||||
c.AddCommand(
|
||||
NewCreateCommand(f),
|
||||
NewGetCommand(f),
|
||||
// Will implement later
|
||||
// NewDescribeCommand(f),
|
||||
NewDeleteCommand(f),
|
||||
)
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// CheckError prints err to stderr and exits with code 1 if err is not nil. Otherwise, it is a
|
||||
// no-op.
|
||||
func CheckError(err error) {
|
||||
if err != nil {
|
||||
if err != context.Canceled {
|
||||
fmt.Fprintf(os.Stderr, fmt.Sprintf("An error occurred: %v\n", err))
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,538 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/endpoints"
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/backup"
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
arkaws "github.com/heptio/ark/pkg/cloudprovider/aws"
|
||||
"github.com/heptio/ark/pkg/cloudprovider/azure"
|
||||
"github.com/heptio/ark/pkg/cloudprovider/gcp"
|
||||
"github.com/heptio/ark/pkg/cmd"
|
||||
"github.com/heptio/ark/pkg/controller"
|
||||
arkdiscovery "github.com/heptio/ark/pkg/discovery"
|
||||
"github.com/heptio/ark/pkg/generated/clientset"
|
||||
arkv1client "github.com/heptio/ark/pkg/generated/clientset/typed/ark/v1"
|
||||
informers "github.com/heptio/ark/pkg/generated/informers/externalversions"
|
||||
"github.com/heptio/ark/pkg/restore"
|
||||
"github.com/heptio/ark/pkg/restore/restorers"
|
||||
"github.com/heptio/ark/pkg/util/kube"
|
||||
)
|
||||
|
||||
func NewCommand() *cobra.Command {
|
||||
var kubeconfig string
|
||||
|
||||
var command = &cobra.Command{
|
||||
Use: "server",
|
||||
Short: "Run the ark server",
|
||||
Long: "Run the ark server",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
s, err := newServer(kubeconfig)
|
||||
cmd.CheckError(err)
|
||||
|
||||
cmd.CheckError(s.run())
|
||||
},
|
||||
}
|
||||
|
||||
command.Flags().StringVar(&kubeconfig, "kubeconfig", "", "Path to the kubeconfig file to use to talk to the Kubernetes apiserver. If unset, try the environment variable KUBECONFIG, as well as in-cluster configuration")
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
type server struct {
|
||||
kubeClient kubernetes.Interface
|
||||
apiExtensionsClient apiextensionsclient.Interface
|
||||
arkClient clientset.Interface
|
||||
backupService cloudprovider.BackupService
|
||||
snapshotService cloudprovider.SnapshotService
|
||||
discoveryClient discovery.DiscoveryInterface
|
||||
clientPool dynamic.ClientPool
|
||||
sharedInformerFactory informers.SharedInformerFactory
|
||||
ctx context.Context
|
||||
cancelFunc context.CancelFunc
|
||||
}
|
||||
|
||||
func newServer(kubeconfig string) (*server, error) {
|
||||
clientConfig, err := client.Config(kubeconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kubeClient, err := kubernetes.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiExtensionsClient, err := apiextensionsclient.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
arkClient, err := clientset.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
|
||||
s := &server{
|
||||
kubeClient: kubeClient,
|
||||
apiExtensionsClient: apiExtensionsClient,
|
||||
arkClient: arkClient,
|
||||
discoveryClient: apiExtensionsClient.Discovery(),
|
||||
clientPool: dynamic.NewDynamicClientPool(clientConfig),
|
||||
sharedInformerFactory: informers.NewSharedInformerFactory(arkClient, 0),
|
||||
ctx: ctx,
|
||||
cancelFunc: cancelFunc,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *server) run() error {
|
||||
if err := s.ensureArkNamespace(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, err := s.loadConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
applyConfigDefaults(config)
|
||||
|
||||
s.watchConfig(config)
|
||||
|
||||
if err := s.initBackupService(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.initSnapshotService(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.runControllers(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) ensureArkNamespace() error {
|
||||
glog.Infof("Ensuring %s namespace exists for backups", api.DefaultNamespace)
|
||||
defaultNamespace := v1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: api.DefaultNamespace,
|
||||
},
|
||||
}
|
||||
|
||||
if created, err := kube.EnsureNamespaceExists(&defaultNamespace, s.kubeClient.CoreV1().Namespaces()); created {
|
||||
glog.Infof("Namespace created")
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
glog.Infof("Namespace already exists")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) loadConfig() (*api.Config, error) {
|
||||
glog.Infof("Retrieving Ark configuration")
|
||||
var (
|
||||
config *api.Config
|
||||
err error
|
||||
)
|
||||
for {
|
||||
config, err = s.arkClient.ArkV1().Configs(api.DefaultNamespace).Get("default", metav1.GetOptions{})
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if !apierrors.IsNotFound(err) {
|
||||
glog.Errorf("error retrieving configuration: %v", err)
|
||||
}
|
||||
glog.Infof("Will attempt to retrieve configuration again in 5 seconds")
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
glog.Infof("Successfully retrieved Ark configuration")
|
||||
return config, nil
|
||||
}
|
||||
|
||||
const (
|
||||
defaultGCSyncPeriod = 60 * time.Minute
|
||||
defaultBackupSyncPeriod = 60 * time.Minute
|
||||
defaultScheduleSyncPeriod = time.Minute
|
||||
)
|
||||
|
||||
var defaultResourcePriorities = []string{
|
||||
"namespaces",
|
||||
"persistentvolumes",
|
||||
"persistentvolumeclaims",
|
||||
"secrets",
|
||||
"configmaps",
|
||||
}
|
||||
|
||||
func applyConfigDefaults(c *api.Config) {
|
||||
if c.GCSyncPeriod.Duration == 0 {
|
||||
c.GCSyncPeriod.Duration = defaultGCSyncPeriod
|
||||
}
|
||||
|
||||
if c.BackupSyncPeriod.Duration == 0 {
|
||||
c.BackupSyncPeriod.Duration = defaultBackupSyncPeriod
|
||||
}
|
||||
|
||||
if c.ScheduleSyncPeriod.Duration == 0 {
|
||||
c.ScheduleSyncPeriod.Duration = defaultScheduleSyncPeriod
|
||||
}
|
||||
|
||||
if len(c.ResourcePriorities) == 0 {
|
||||
c.ResourcePriorities = defaultResourcePriorities
|
||||
glog.Infof("Using default resource priorities: %v", c.ResourcePriorities)
|
||||
} else {
|
||||
glog.Infof("Using resource priorities from config: %v", c.ResourcePriorities)
|
||||
}
|
||||
}
|
||||
|
||||
// watchConfig adds an update event handler to the Config shared informer, invoking s.cancelFunc
|
||||
// when it sees a change.
|
||||
func (s *server) watchConfig(config *api.Config) {
|
||||
s.sharedInformerFactory.Ark().V1().Configs().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
UpdateFunc: func(oldObj, newObj interface{}) {
|
||||
updated := newObj.(*api.Config)
|
||||
|
||||
if updated.Name != config.Name {
|
||||
glog.V(5).Infof("config watch channel received other config %q", updated.Name)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(config, updated) {
|
||||
glog.Infof("Detected a config change. Gracefully shutting down")
|
||||
s.cancelFunc()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *server) initBackupService(config *api.Config) error {
|
||||
glog.Infof("Configuring cloud provider for backup service")
|
||||
cloud, err := initCloud(config.BackupStorageProvider.CloudProviderConfig, "backupStorageProvider")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.backupService = cloudprovider.NewBackupService(cloud.ObjectStorage())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) initSnapshotService(config *api.Config) error {
|
||||
glog.Infof("Configuring cloud provider for snapshot service")
|
||||
cloud, err := initCloud(config.PersistentVolumeProvider, "persistentVolumeProvider")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.snapshotService = cloudprovider.NewSnapshotService(cloud.BlockStorage())
|
||||
return nil
|
||||
}
|
||||
|
||||
func initCloud(config api.CloudProviderConfig, field string) (cloudprovider.StorageAdapter, error) {
|
||||
var (
|
||||
cloud cloudprovider.StorageAdapter
|
||||
err error
|
||||
)
|
||||
|
||||
if config.AWS != nil {
|
||||
cloud, err = getAWSCloudProvider(config)
|
||||
}
|
||||
|
||||
if config.GCP != nil {
|
||||
if cloud != nil {
|
||||
return nil, fmt.Errorf("you may only specify one of aws, gcp, or azure for %s", field)
|
||||
}
|
||||
cloud, err = getGCPCloudProvider(config)
|
||||
}
|
||||
|
||||
if config.Azure != nil {
|
||||
if cloud != nil {
|
||||
return nil, fmt.Errorf("you may only specify one of aws, gcp, or azure for %s", field)
|
||||
}
|
||||
cloud, err = getAzureCloudProvider(config)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cloud == nil {
|
||||
return nil, fmt.Errorf("you must specify one of aws, gcp, or azure for %s", field)
|
||||
}
|
||||
|
||||
return cloud, err
|
||||
}
|
||||
|
||||
func getAWSCloudProvider(cloudConfig api.CloudProviderConfig) (cloudprovider.StorageAdapter, error) {
|
||||
if cloudConfig.AWS == nil {
|
||||
return nil, errors.New("missing aws configuration in config file")
|
||||
}
|
||||
if cloudConfig.AWS.Region == "" {
|
||||
return nil, errors.New("missing region in aws configuration in config file")
|
||||
}
|
||||
if cloudConfig.AWS.AvailabilityZone == "" {
|
||||
return nil, errors.New("missing availabilityZone in aws configuration in config file")
|
||||
}
|
||||
|
||||
awsConfig := aws.NewConfig().
|
||||
WithRegion(cloudConfig.AWS.Region).
|
||||
WithS3ForcePathStyle(cloudConfig.AWS.S3ForcePathStyle)
|
||||
|
||||
if cloudConfig.AWS.S3Url != "" {
|
||||
awsConfig = awsConfig.WithEndpointResolver(
|
||||
endpoints.ResolverFunc(func(service, region string, optFns ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) {
|
||||
if service == endpoints.S3ServiceID {
|
||||
return endpoints.ResolvedEndpoint{
|
||||
URL: cloudConfig.AWS.S3Url,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return endpoints.DefaultResolver().EndpointFor(service, region, optFns...)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return arkaws.NewStorageAdapter(awsConfig, cloudConfig.AWS.AvailabilityZone)
|
||||
}
|
||||
|
||||
func getGCPCloudProvider(cloudConfig api.CloudProviderConfig) (cloudprovider.StorageAdapter, error) {
|
||||
if cloudConfig.GCP == nil {
|
||||
return nil, errors.New("missing gcp configuration in config file")
|
||||
}
|
||||
if cloudConfig.GCP.Project == "" {
|
||||
return nil, errors.New("missing project in gcp configuration in config file")
|
||||
}
|
||||
if cloudConfig.GCP.Zone == "" {
|
||||
return nil, errors.New("missing zone in gcp configuration in config file")
|
||||
}
|
||||
return gcp.NewStorageAdapter(cloudConfig.GCP.Project, cloudConfig.GCP.Zone)
|
||||
}
|
||||
|
||||
func getAzureCloudProvider(cloudConfig api.CloudProviderConfig) (cloudprovider.StorageAdapter, error) {
|
||||
if cloudConfig.Azure == nil {
|
||||
return nil, errors.New("missing azure configuration in config file")
|
||||
}
|
||||
if cloudConfig.Azure.Location == "" {
|
||||
return nil, errors.New("missing location in azure configuration in config file")
|
||||
}
|
||||
return azure.NewStorageAdapter(cloudConfig.Azure.Location, cloudConfig.Azure.APITimeout.Duration)
|
||||
}
|
||||
|
||||
func durationMin(a, b time.Duration) time.Duration {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (s *server) runControllers(config *api.Config) error {
|
||||
glog.Infof("Starting controllers")
|
||||
|
||||
ctx := s.ctx
|
||||
var wg sync.WaitGroup
|
||||
|
||||
cloudBackupCacheResyncPeriod := durationMin(config.GCSyncPeriod.Duration, config.BackupSyncPeriod.Duration)
|
||||
glog.Infof("Caching cloud backups every %s", cloudBackupCacheResyncPeriod)
|
||||
s.backupService = cloudprovider.NewBackupServiceWithCachedBackupGetter(
|
||||
ctx,
|
||||
s.backupService,
|
||||
cloudBackupCacheResyncPeriod,
|
||||
)
|
||||
|
||||
backupSyncController := controller.NewBackupSyncController(
|
||||
s.arkClient.ArkV1(),
|
||||
s.backupService,
|
||||
config.BackupStorageProvider.Bucket,
|
||||
config.BackupSyncPeriod.Duration,
|
||||
)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
backupSyncController.Run(ctx, 1)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
discoveryHelper, err := arkdiscovery.NewHelper(s.discoveryClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go wait.Until(
|
||||
func() {
|
||||
if err := discoveryHelper.Refresh(); err != nil {
|
||||
glog.Errorf("error refreshing discovery: %v", err)
|
||||
}
|
||||
},
|
||||
5*time.Minute,
|
||||
ctx.Done(),
|
||||
)
|
||||
|
||||
if config.RestoreOnlyMode {
|
||||
glog.Infof("Restore only mode - not starting the backup, schedule or GC controllers")
|
||||
} else {
|
||||
backupper, err := newBackupper(discoveryHelper, s.clientPool, s.backupService, s.snapshotService)
|
||||
cmd.CheckError(err)
|
||||
backupController := controller.NewBackupController(
|
||||
s.sharedInformerFactory.Ark().V1().Backups(),
|
||||
s.arkClient.ArkV1(),
|
||||
backupper,
|
||||
s.backupService,
|
||||
config.BackupStorageProvider.Bucket,
|
||||
)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
backupController.Run(ctx, 1)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
scheduleController := controller.NewScheduleController(
|
||||
s.arkClient.ArkV1(),
|
||||
s.arkClient.ArkV1(),
|
||||
s.sharedInformerFactory.Ark().V1().Schedules(),
|
||||
config.ScheduleSyncPeriod.Duration,
|
||||
)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
scheduleController.Run(ctx, 1)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
gcController := controller.NewGCController(
|
||||
s.backupService,
|
||||
s.snapshotService,
|
||||
config.BackupStorageProvider.Bucket,
|
||||
config.GCSyncPeriod.Duration,
|
||||
s.sharedInformerFactory.Ark().V1().Backups(),
|
||||
s.arkClient.ArkV1(),
|
||||
)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
gcController.Run(ctx, 1)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
restorer, err := newRestorer(
|
||||
discoveryHelper,
|
||||
s.clientPool,
|
||||
s.backupService,
|
||||
s.snapshotService,
|
||||
config.ResourcePriorities,
|
||||
s.arkClient.ArkV1(),
|
||||
s.kubeClient,
|
||||
)
|
||||
cmd.CheckError(err)
|
||||
|
||||
restoreController := controller.NewRestoreController(
|
||||
s.sharedInformerFactory.Ark().V1().Restores(),
|
||||
s.arkClient.ArkV1(),
|
||||
s.arkClient.ArkV1(),
|
||||
restorer,
|
||||
s.backupService,
|
||||
config.BackupStorageProvider.Bucket,
|
||||
s.sharedInformerFactory.Ark().V1().Backups(),
|
||||
)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
restoreController.Run(ctx, 1)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
// SHARED INFORMERS HAVE TO BE STARTED AFTER ALL CONTROLLERS
|
||||
go s.sharedInformerFactory.Start(ctx.Done())
|
||||
|
||||
glog.Infof("Server started successfully")
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
glog.Info("Waiting for all controllers to shut down gracefully")
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newBackupper(
|
||||
discoveryHelper arkdiscovery.Helper,
|
||||
clientPool dynamic.ClientPool,
|
||||
backupService cloudprovider.BackupService,
|
||||
snapshotService cloudprovider.SnapshotService,
|
||||
) (backup.Backupper, error) {
|
||||
actions := map[string]backup.Action{}
|
||||
|
||||
if snapshotService != nil {
|
||||
actions["persistentvolumes"] = backup.NewVolumeSnapshotAction(snapshotService)
|
||||
}
|
||||
|
||||
return backup.NewKubernetesBackupper(
|
||||
discoveryHelper,
|
||||
client.NewDynamicFactory(clientPool),
|
||||
actions,
|
||||
)
|
||||
}
|
||||
|
||||
func newRestorer(
|
||||
discoveryHelper arkdiscovery.Helper,
|
||||
clientPool dynamic.ClientPool,
|
||||
backupService cloudprovider.BackupService,
|
||||
snapshotService cloudprovider.SnapshotService,
|
||||
resourcePriorities []string,
|
||||
backupClient arkv1client.BackupsGetter,
|
||||
kubeClient kubernetes.Interface,
|
||||
) (restore.Restorer, error) {
|
||||
restorers := map[string]restorers.ResourceRestorer{
|
||||
"persistentvolumes": restorers.NewPersistentVolumeRestorer(snapshotService),
|
||||
"persistentvolumeclaims": restorers.NewPersistentVolumeClaimRestorer(),
|
||||
"services": restorers.NewServiceRestorer(),
|
||||
"namespaces": restorers.NewNamespaceRestorer(),
|
||||
"pods": restorers.NewPodRestorer(),
|
||||
"jobs": restorers.NewJobRestorer(),
|
||||
}
|
||||
|
||||
return restore.NewKubernetesRestorer(
|
||||
discoveryHelper,
|
||||
client.NewDynamicFactory(clientPool),
|
||||
restorers,
|
||||
backupService,
|
||||
resourcePriorities,
|
||||
backupClient,
|
||||
kubeClient.CoreV1().Namespaces(),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 server
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
)
|
||||
|
||||
func TestApplyConfigDefaults(t *testing.T) {
|
||||
c := &v1.Config{}
|
||||
|
||||
// test defaulting
|
||||
applyConfigDefaults(c)
|
||||
assert.Equal(t, defaultGCSyncPeriod, c.GCSyncPeriod.Duration)
|
||||
assert.Equal(t, defaultBackupSyncPeriod, c.BackupSyncPeriod.Duration)
|
||||
assert.Equal(t, defaultScheduleSyncPeriod, c.ScheduleSyncPeriod.Duration)
|
||||
assert.Equal(t, defaultResourcePriorities, c.ResourcePriorities)
|
||||
|
||||
// make sure defaulting doesn't overwrite real values
|
||||
c.GCSyncPeriod.Duration = 5 * time.Minute
|
||||
c.BackupSyncPeriod.Duration = 4 * time.Minute
|
||||
c.ScheduleSyncPeriod.Duration = 3 * time.Minute
|
||||
c.ResourcePriorities = []string{"a", "b"}
|
||||
|
||||
applyConfigDefaults(c)
|
||||
|
||||
assert.Equal(t, 5*time.Minute, c.GCSyncPeriod.Duration)
|
||||
assert.Equal(t, 4*time.Minute, c.BackupSyncPeriod.Duration)
|
||||
assert.Equal(t, 3*time.Minute, c.ScheduleSyncPeriod.Duration)
|
||||
assert.Equal(t, []string{"a", "b"}, c.ResourcePriorities)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 flag
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// GetOptionalStringFlag returns the value of the specified flag from a
|
||||
// cobra command, or the zero value ("") if the flag was not specified.
|
||||
func GetOptionalStringFlag(cmd *cobra.Command, flagName string) string {
|
||||
return GetStringFlag(cmd, flagName, false)
|
||||
}
|
||||
|
||||
// GetStringFlag returns the value of the specified flag from a
|
||||
// cobra command. If the flag is not specified and fatalIfMissing is true,
|
||||
// this function logs a fatal error and calls os.Exit(255).
|
||||
func GetStringFlag(cmd *cobra.Command, flagName string, fatalIfMissing bool) string {
|
||||
s, err := cmd.Flags().GetString(flagName)
|
||||
if err != nil && fatalIfMissing {
|
||||
glog.Fatalf("error accessing flag %q for command %s: %v", flagName, cmd.Name(), err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// GetOptionalBoolFlag returns the value of the specified flag from a
|
||||
// cobra command, or the zero value (false) if the flag was not specified.
|
||||
func GetOptionalBoolFlag(cmd *cobra.Command, flagName string) bool {
|
||||
return GetBoolFlag(cmd, flagName, false)
|
||||
}
|
||||
|
||||
// GetBoolFlag returns the value of the specified flag from a
|
||||
// cobra command. If the flag is not specified and fatalIfMissing is true,
|
||||
// this function logs a fatal error and calls os.Exit(255).
|
||||
func GetBoolFlag(cmd *cobra.Command, flagName string, fatalIfMissing bool) bool {
|
||||
b, err := cmd.Flags().GetBool(flagName)
|
||||
if err != nil && fatalIfMissing {
|
||||
glog.Fatalf("error accessing flag %q for command %s: %v", flagName, cmd.Name(), err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// GetOptionalStringArrayFlag returns the value of the specified flag from a
|
||||
// cobra command, or the zero value if the flag was not specified.
|
||||
func GetOptionalStringArrayFlag(cmd *cobra.Command, flagName string) []string {
|
||||
return GetStringArrayFlag(cmd, flagName, false)
|
||||
}
|
||||
|
||||
// GetStringArrayFlag returns the value of the specified flag from a
|
||||
// cobra command. If the flag is not specified and fatalIfMissing is true,
|
||||
// this function logs a fatal error and calls os.Exit(255).
|
||||
func GetStringArrayFlag(cmd *cobra.Command, flagName string, fatalIfMissing bool) []string {
|
||||
f := cmd.Flag(flagName)
|
||||
if f == nil {
|
||||
if fatalIfMissing {
|
||||
glog.Fatalf("error accessing flag %q for command %s: not specified", flagName, cmd.Name())
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
v := f.Value.(*StringArray)
|
||||
return *v
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 flag
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StringArray is a Cobra-compatible named type for defining a
|
||||
// string slice flag.
|
||||
type StringArray []string
|
||||
|
||||
// NewStringArray returns a StringArray for a provided
|
||||
// slice of values.
|
||||
func NewStringArray(initial ...string) StringArray {
|
||||
return StringArray(initial)
|
||||
}
|
||||
|
||||
// String returns a comma-separated list of the items
|
||||
// in the string array.
|
||||
func (sa *StringArray) String() string {
|
||||
return strings.Join(*sa, ",")
|
||||
}
|
||||
|
||||
// Set comma-splits the provided string and assigns
|
||||
// the results to the receiver. It returns an error if
|
||||
// the string is not parseable.
|
||||
func (sa *StringArray) Set(s string) error {
|
||||
*sa = strings.Split(s, ",")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns a string representation of the
|
||||
// StringArray type.
|
||||
func (sa *StringArray) Type() string {
|
||||
return "stringArray"
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 flag
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// LabelSelector is a Cobra-compatible wrapper for defining
|
||||
// a Kubernetes label-selector flag.
|
||||
type LabelSelector struct {
|
||||
LabelSelector *metav1.LabelSelector
|
||||
}
|
||||
|
||||
// String returns a string representation of the label
|
||||
// selector flag.
|
||||
func (ls *LabelSelector) String() string {
|
||||
return metav1.FormatLabelSelector(ls.LabelSelector)
|
||||
}
|
||||
|
||||
// Set parses the provided string and assigns the result
|
||||
// to the label-selector receiver. It returns an error if
|
||||
// the string is not parseable.
|
||||
func (ls *LabelSelector) Set(s string) error {
|
||||
parsed, err := metav1.ParseToLabelSelector(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ls.LabelSelector = parsed
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns a string representation of the
|
||||
// LabelSelector type.
|
||||
func (ls *LabelSelector) Type() string {
|
||||
return "labelSelector"
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 flag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Map is a Cobra-compatible wrapper for defining a flag containing
|
||||
// map data (i.e. a collection of key-value pairs).
|
||||
type Map struct {
|
||||
data map[string]string
|
||||
entryDelimiter string
|
||||
keyValueDelimiter string
|
||||
}
|
||||
|
||||
// NewMap returns a Map using the default delimiters ("=" between keys and
|
||||
// values, and "," between map entries, e.g. k1=v1,k2=v2)
|
||||
func NewMap() Map {
|
||||
m := Map{
|
||||
data: make(map[string]string),
|
||||
}
|
||||
|
||||
return m.WithEntryDelimiter(",").WithKeyValueDelimiter("=")
|
||||
}
|
||||
|
||||
// WithEntryDelimiter sets the delimiter to be used between map
|
||||
// entries.
|
||||
//
|
||||
// For example, in "k1=v1&k2=v2", the entry delimiter is "&"
|
||||
func (m Map) WithEntryDelimiter(delimiter string) Map {
|
||||
m.entryDelimiter = delimiter
|
||||
return m
|
||||
}
|
||||
|
||||
// WithKeyValueDelimiter sets the delimiter to be used between
|
||||
// keys and values.
|
||||
//
|
||||
// For example, in "k1=v1&k2=v2", the key-value delimiter is "="
|
||||
func (m Map) WithKeyValueDelimiter(delimiter string) Map {
|
||||
m.keyValueDelimiter = delimiter
|
||||
return m
|
||||
}
|
||||
|
||||
// String returns a string representation of the Map flag.
|
||||
func (m *Map) String() string {
|
||||
var a []string
|
||||
for k, v := range m.data {
|
||||
a = append(a, fmt.Sprintf("%s%s%s", k, m.keyValueDelimiter, v))
|
||||
}
|
||||
return strings.Join(a, m.entryDelimiter)
|
||||
}
|
||||
|
||||
// Set parses the provided string according to the delimiters and
|
||||
// assigns the result to the Map receiver. It returns an error if
|
||||
// the string is not parseable.
|
||||
func (m *Map) Set(s string) error {
|
||||
for _, part := range strings.Split(s, m.entryDelimiter) {
|
||||
kvs := strings.SplitN(part, m.keyValueDelimiter, 2)
|
||||
if len(kvs) != 2 {
|
||||
return fmt.Errorf("error parsing %q", part)
|
||||
}
|
||||
m.data[kvs[0]] = kvs[1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns a string representation of the
|
||||
// Map type.
|
||||
func (m *Map) Type() string {
|
||||
return "mapStringString"
|
||||
}
|
||||
|
||||
// Data returns the underlying golang map storing
|
||||
// the flag data.
|
||||
func (m *Map) Data() map[string]string {
|
||||
return m.data
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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"
|
||||
"regexp"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
backupColumns = []string{"NAME", "STATUS", "CREATED", "EXPIRES", "SELECTOR"}
|
||||
)
|
||||
|
||||
func printBackupList(list *v1.BackupList, w io.Writer, options printers.PrintOptions) error {
|
||||
sortBackupsByPrefixAndTimestamp(list)
|
||||
|
||||
for i := range list.Items {
|
||||
if err := printBackup(&list.Items[i], w, options); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sortBackupsByPrefixAndTimestamp(list *v1.BackupList) {
|
||||
// sort by default alphabetically, but if backups stem from a common schedule
|
||||
// (detected by the presence of a 14-digit timestamp suffix), then within that
|
||||
// group, sort by newest to oldest (i.e. prefix ASC, suffix DESC)
|
||||
timestampSuffix := regexp.MustCompile("-[0-9]{14}$")
|
||||
|
||||
sort.Slice(list.Items, func(i, j int) bool {
|
||||
iSuffixIndex := timestampSuffix.FindStringIndex(list.Items[i].Name)
|
||||
jSuffixIndex := timestampSuffix.FindStringIndex(list.Items[j].Name)
|
||||
|
||||
// one/both don't have a timestamp suffix, so sort alphabetically
|
||||
if iSuffixIndex == nil || jSuffixIndex == nil {
|
||||
return list.Items[i].Name < list.Items[j].Name
|
||||
}
|
||||
|
||||
// different prefixes, so sort alphabetically
|
||||
if list.Items[i].Name[0:iSuffixIndex[0]] != list.Items[j].Name[0:jSuffixIndex[0]] {
|
||||
return list.Items[i].Name < list.Items[j].Name
|
||||
}
|
||||
|
||||
// same prefixes, so sort based on suffix (desc)
|
||||
return list.Items[i].Name[iSuffixIndex[0]:] >= list.Items[j].Name[jSuffixIndex[0]:]
|
||||
})
|
||||
}
|
||||
|
||||
func printBackup(backup *v1.Backup, w io.Writer, options printers.PrintOptions) error {
|
||||
name := printers.FormatResourceName(options.Kind, backup.Name, options.WithKind)
|
||||
|
||||
if options.WithNamespace {
|
||||
if _, err := fmt.Fprintf(w, "%s\t", backup.Namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
expiration := backup.Status.Expiration.Time
|
||||
if expiration.IsZero() && backup.Spec.TTL.Duration > 0 {
|
||||
expiration = backup.CreationTimestamp.Add(backup.Spec.TTL.Duration)
|
||||
}
|
||||
|
||||
status := backup.Status.Phase
|
||||
if status == "" {
|
||||
status = v1.BackupPhaseNew
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s", name, status, backup.CreationTimestamp.Time, humanReadableTimeFromNow(expiration), metav1.FormatLabelSelector(backup.Spec.LabelSelector)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprint(w, printers.AppendLabels(backup.Labels, options.ColumnLabels)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := fmt.Fprint(w, printers.AppendAllLabels(options.ShowLabels, backup.Labels))
|
||||
return err
|
||||
}
|
||||
|
||||
func humanReadableTimeFromNow(when time.Time) string {
|
||||
if when.IsZero() {
|
||||
return "n/a"
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
switch {
|
||||
case when == now || when.After(now):
|
||||
return printers.ShortHumanDuration(when.Sub(now))
|
||||
default:
|
||||
return fmt.Sprintf("%s ago", printers.ShortHumanDuration(now.Sub(when)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
)
|
||||
|
||||
func TestSortBackups(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
backupList *v1.BackupList
|
||||
expected []v1.Backup
|
||||
}{
|
||||
{
|
||||
name: "non-timestamped backups",
|
||||
backupList: &v1.BackupList{Items: []v1.Backup{
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "a"}},
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "c"}},
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "b"}},
|
||||
}},
|
||||
expected: []v1.Backup{
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "a"}},
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "b"}},
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "c"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "timestamped backups",
|
||||
backupList: &v1.BackupList{Items: []v1.Backup{
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "schedule-20170102030405"}},
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "schedule-20170102030406"}},
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "schedule-20170102030407"}},
|
||||
}},
|
||||
expected: []v1.Backup{
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "schedule-20170102030407"}},
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "schedule-20170102030406"}},
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "schedule-20170102030405"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-timestamped and timestamped backups",
|
||||
backupList: &v1.BackupList{Items: []v1.Backup{
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "schedule-20170102030405"}},
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "schedule-20170102030406"}},
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "a"}},
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "schedule-20170102030407"}},
|
||||
}},
|
||||
expected: []v1.Backup{
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "a"}},
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "schedule-20170102030407"}},
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "schedule-20170102030406"}},
|
||||
v1.Backup{ObjectMeta: metav1.ObjectMeta{Name: "schedule-20170102030405"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
sortBackupsByPrefixAndTimestamp(test.backupList)
|
||||
|
||||
if assert.Equal(t, len(test.backupList.Items), len(test.expected)) {
|
||||
for i := range test.expected {
|
||||
assert.Equal(t, test.expected[i].Name, test.backupList.Items[i].Name)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
|
||||
"github.com/heptio/ark/pkg/cmd/util/flag"
|
||||
"github.com/heptio/ark/pkg/generated/clientset/scheme"
|
||||
"github.com/heptio/ark/pkg/util/encode"
|
||||
)
|
||||
|
||||
// BindFlags defines a set of output-specific flags within the provided
|
||||
// FlagSet.
|
||||
func BindFlags(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'.")
|
||||
labelColumns := flag.NewStringArray()
|
||||
flags.Var(&labelColumns, "label-columns", "a comma-separated list of labels to be displayed as columns")
|
||||
flags.Bool("show-labels", false, "show labels in the last column")
|
||||
}
|
||||
|
||||
// ClearOutputFlagDefault sets the current and default value
|
||||
// of the "output" flag to the empty string.
|
||||
func ClearOutputFlagDefault(cmd *cobra.Command) {
|
||||
f := cmd.Flag("output")
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
f.DefValue = ""
|
||||
f.Value.Set("")
|
||||
}
|
||||
|
||||
// GetOutputFlagValue returns the value of the "output" flag
|
||||
// in the provided command, or the zero value if not present.
|
||||
func GetOutputFlagValue(cmd *cobra.Command) string {
|
||||
return flag.GetOptionalStringFlag(cmd, "output")
|
||||
}
|
||||
|
||||
// GetLabelColumnsValues returns the value of the "label-columns" flag
|
||||
// in the provided command, or the zero value if not present.
|
||||
func GetLabelColumnsValues(cmd *cobra.Command) []string {
|
||||
return flag.GetOptionalStringArrayFlag(cmd, "label-columns")
|
||||
}
|
||||
|
||||
// GetShowLabelsValue returns the value of the "show-labels" flag
|
||||
// in the provided command, or the zero value if not present.
|
||||
func GetShowLabelsValue(cmd *cobra.Command) bool {
|
||||
return flag.GetOptionalBoolFlag(cmd, "show-labels")
|
||||
}
|
||||
|
||||
// ValidateFlags returns an error if any of the output-related flags
|
||||
// were specified with invalid values, or nil otherwise.
|
||||
func ValidateFlags(cmd *cobra.Command) error {
|
||||
if err := validateOutputFlag(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateOutputFlag(cmd *cobra.Command) error {
|
||||
output := GetOutputFlagValue(cmd)
|
||||
switch output {
|
||||
case "", "table", "json", "yaml":
|
||||
default:
|
||||
return fmt.Errorf("invalid output format %q - valid values are 'table', 'json', and 'yaml'", output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrintWithFormat prints the provided object in the format specified by
|
||||
// the command's flags.
|
||||
func PrintWithFormat(c *cobra.Command, obj runtime.Object) (bool, error) {
|
||||
format := GetOutputFlagValue(c)
|
||||
if format == "" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
switch format {
|
||||
case "table":
|
||||
return printTable(c, obj)
|
||||
case "json", "yaml":
|
||||
return printEncoded(obj, format)
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("unsupported output format %q; valid values are 'table', 'json', and 'yaml'", format)
|
||||
}
|
||||
|
||||
func printEncoded(obj runtime.Object, format string) (bool, error) {
|
||||
// assume we're printing obj
|
||||
toPrint := obj
|
||||
|
||||
if meta.IsListType(obj) {
|
||||
list, _ := meta.ExtractList(obj)
|
||||
if len(list) == 1 {
|
||||
// if obj was a list and there was only 1 item, just print that 1 instead of a list
|
||||
toPrint = list[0]
|
||||
}
|
||||
}
|
||||
|
||||
encoded, err := encode.Encode(toPrint, format)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
fmt.Println(string(encoded))
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func printTable(cmd *cobra.Command, obj runtime.Object) (bool, error) {
|
||||
printer, err := NewPrinter(cmd)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
printer.Handler(backupColumns, nil, printBackup)
|
||||
printer.Handler(backupColumns, nil, printBackupList)
|
||||
printer.Handler(restoreColumns, nil, printRestore)
|
||||
printer.Handler(restoreColumns, nil, printRestoreList)
|
||||
printer.Handler(scheduleColumns, nil, printSchedule)
|
||||
printer.Handler(scheduleColumns, nil, printScheduleList)
|
||||
|
||||
err = printer.PrintObj(obj, os.Stdout)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// NewPrinter returns a printer for doing human-readable table printing of
|
||||
// Ark objects.
|
||||
func NewPrinter(cmd *cobra.Command) (*printers.HumanReadablePrinter, error) {
|
||||
encoder, err := encode.EncoderFor("json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
options := printers.PrintOptions{
|
||||
NoHeaders: flag.GetOptionalBoolFlag(cmd, "no-headers"),
|
||||
ShowLabels: GetShowLabelsValue(cmd),
|
||||
ColumnLabels: GetLabelColumnsValues(cmd),
|
||||
}
|
||||
|
||||
printer := printers.NewHumanReadablePrinter(
|
||||
encoder,
|
||||
scheme.Codecs.UniversalDecoder(api.SchemeGroupVersion),
|
||||
options,
|
||||
)
|
||||
|
||||
return printer, nil
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
restoreColumns = []string{"NAME", "BACKUP", "STATUS", "WARNINGS", "ERRORS", "CREATED", "SELECTOR"}
|
||||
)
|
||||
|
||||
func printRestoreList(list *v1.RestoreList, w io.Writer, options printers.PrintOptions) error {
|
||||
for i := range list.Items {
|
||||
if err := printRestore(&list.Items[i], w, options); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printRestore(restore *v1.Restore, w io.Writer, options printers.PrintOptions) error {
|
||||
name := printers.FormatResourceName(options.Kind, restore.Name, options.WithKind)
|
||||
|
||||
if options.WithNamespace {
|
||||
if _, err := fmt.Fprintf(w, "%s\t", restore.Namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
status := restore.Status.Phase
|
||||
if status == "" {
|
||||
status = v1.RestorePhaseNew
|
||||
}
|
||||
|
||||
warnings := len(restore.Status.Warnings.Ark) + len(restore.Status.Warnings.Cluster)
|
||||
for _, w := range restore.Status.Warnings.Namespaces {
|
||||
warnings += len(w)
|
||||
}
|
||||
errors := len(restore.Status.Errors.Ark) + len(restore.Status.Errors.Cluster)
|
||||
for _, e := range restore.Status.Errors.Namespaces {
|
||||
errors += len(e)
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%d\t%s\t%s", name, restore.Spec.BackupName, status, warnings, errors, restore.CreationTimestamp.Time, metav1.FormatLabelSelector(restore.Spec.LabelSelector)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprint(w, printers.AppendLabels(restore.Labels, options.ColumnLabels)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := fmt.Fprint(w, printers.AppendAllLabels(options.ShowLabels, restore.Labels))
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
scheduleColumns = []string{"NAME", "STATUS", "CREATED", "SCHEDULE", "BACKUP TTL", "LAST BACKUP", "SELECTOR"}
|
||||
)
|
||||
|
||||
func printScheduleList(list *v1.ScheduleList, w io.Writer, options printers.PrintOptions) error {
|
||||
for i := range list.Items {
|
||||
if err := printSchedule(&list.Items[i], w, options); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printSchedule(schedule *v1.Schedule, w io.Writer, options printers.PrintOptions) error {
|
||||
name := printers.FormatResourceName(options.Kind, schedule.Name, options.WithKind)
|
||||
|
||||
if options.WithNamespace {
|
||||
if _, err := fmt.Fprintf(w, "%s\t", schedule.Namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
status := schedule.Status.Phase
|
||||
if status == "" {
|
||||
status = v1.SchedulePhaseNew
|
||||
}
|
||||
|
||||
_, err := fmt.Fprintf(
|
||||
w,
|
||||
"%s\t%s\t%s\t%s\t%s\t%s\t%s",
|
||||
name,
|
||||
status,
|
||||
schedule.CreationTimestamp.Time,
|
||||
schedule.Spec.Schedule,
|
||||
schedule.Spec.Template.TTL.Duration,
|
||||
humanReadableTimeFromNow(schedule.Status.LastBackup.Time),
|
||||
metav1.FormatLabelSelector(schedule.Spec.Template.LabelSelector),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprint(w, printers.AppendLabels(schedule.Labels, options.ColumnLabels)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprint(w, printers.AppendAllLabels(options.ShowLabels, schedule.Labels))
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/heptio/ark/pkg/buildinfo"
|
||||
)
|
||||
|
||||
func NewCommand() *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the ark version and associated image",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println(buildinfo.Version)
|
||||
fmt.Println("Configured docker image:", buildinfo.DockerImage)
|
||||
},
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
Reference in New Issue
Block a user