Initial commit

Signed-off-by: Andy Goldstein <andy.goldstein@gmail.com>
This commit is contained in:
Andy Goldstein
2017-08-02 13:27:17 -04:00
commit 2fe501f527
2024 changed files with 948288 additions and 0 deletions
+57
View File
@@ -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
}
+45
View File
@@ -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
}
+138
View File
@@ -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
}
+53
View File
@@ -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
}
+34
View File
@@ -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
}
+66
View File
@@ -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
}
+38
View File
@@ -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
}
+29
View File
@@ -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
}
+29
View File
@@ -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
}
+129
View File
@@ -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
}
+53
View File
@@ -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
}
+34
View File
@@ -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
}
+71
View File
@@ -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
}
+41
View File
@@ -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
}
+124
View File
@@ -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
}
+53
View File
@@ -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
}
+34
View File
@@ -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
}
+71
View File
@@ -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
}
+41
View File
@@ -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
}
+34
View File
@@ -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)
}
}
+538
View File
@@ -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(),
)
}
+50
View File
@@ -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)
}
+77
View File
@@ -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
}
+51
View File
@@ -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"
}
+51
View File
@@ -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"
}
+93
View File
@@ -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
}
+115
View File
@@ -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)
}
}
})
}
}
+175
View File
@@ -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
}
+74
View File
@@ -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
}
+78
View File
@@ -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
}
+38
View File
@@ -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
}