diff --git a/Makefile b/Makefile index 17e52ab2..2757b647 100644 --- a/Makefile +++ b/Makefile @@ -57,7 +57,7 @@ docker-images: clean docker manifest push $(IMAGE_NAME):$(IMAGE_VERSION) lint: - @GO111MODULE=off go get -u -v golang.org/x/lint/golint + @GO111MODULE=auto go get -u -v golang.org/x/lint/golint @for file in $$(go list ./... | grep -v '_workspace/' | grep -v 'vendor'); do \ export output="$$(golint $${file} | grep -v 'type name will be used as docker.DockerInfo')"; \ [ -n "$${output}" ] && echo "$${output}" && export status=1; \ diff --git a/cmd/manager/manager.go b/cmd/manager/manager.go index 00b5a11e..45521874 100644 --- a/cmd/manager/manager.go +++ b/cmd/manager/manager.go @@ -35,6 +35,7 @@ var ( parallelCount int refreshRate string backupInterval string + backupTimeSpec string ) var envs = make(map[string]string) @@ -59,7 +60,7 @@ var managerCmd = &cobra.Command{ agentImage = fmt.Sprintf("ghcr.io/camptocamp/bivac:%s", utils.ComputeDockerAgentImage(managerVersion)) } - err = manager.Start(bivacCmd.BuildInfo, o, server, volumesFilters, providersFile, targetURL, logServer, agentImage, retryCount, parallelCount, refreshRate, backupInterval) + err = manager.Start(bivacCmd.BuildInfo, o, server, volumesFilters, providersFile, targetURL, logServer, agentImage, retryCount, parallelCount, refreshRate, backupInterval, backupTimeSpec) if err != nil { log.Errorf("failed to start manager: %s", err) return @@ -92,6 +93,8 @@ func init() { envs["KUBERNETES_NAMESPACE"] = "kubernetes.namespace" managerCmd.Flags().BoolVarP(&Orchestrators.Kubernetes.AllNamespaces, "kubernetes.all-namespaces", "", false, "Backup volumes of all namespaces.") envs["KUBERNETES_ALL_NAMESPACES"] = "kubernetes.all-namespaces" + managerCmd.Flags().StringVarP(&Orchestrators.Kubernetes.CustomNamespaces, "kubernetes.custom-namespaces", "", "", "Backup volumes from a custom namespaces list.") + envs["KUBERNETES_CUSTOM_NAMESPACES"] = "kubernetes.custom-namespaces" managerCmd.Flags().StringVarP(&Orchestrators.Kubernetes.KubeConfig, "kubernetes.kubeconfig", "", "", "Path to your kuberconfig file.") envs["KUBERNETES_KUBECONFIG"] = "kubernetes.kubeconfig" managerCmd.Flags().StringVarP(&Orchestrators.Kubernetes.AgentServiceAccount, "kubernetes.agent-service-account", "", "", "Specify service account for agents.") @@ -139,6 +142,9 @@ func init() { managerCmd.Flags().StringVarP(&backupInterval, "backup.interval", "", "23h", "Interval between two backups of a volume.") envs["BIVAC_BACKUP_INTERVAL"] = "backup.interval" + managerCmd.Flags().StringVarP(&backupTimeSpec, "backup.time", "", "00h", "Prefer time to do the backup.") + envs["BIVAC_BACKUP_TIME"] = "backup.time" + bivacCmd.SetValuesFromEnv(envs, managerCmd.Flags()) bivacCmd.RootCmd.AddCommand(managerCmd) } diff --git a/internal/manager/manager.go b/internal/manager/manager.go index 27c5bba5..bdab07c3 100644 --- a/internal/manager/manager.go +++ b/internal/manager/manager.go @@ -34,7 +34,7 @@ type Manager struct { } // Start starts a Bivac manager which handle backups management -func Start(buildInfo utils.BuildInfo, o orchestrators.Orchestrator, s Server, volumeFilters volume.Filters, providersFile, targetURL, logServer, agentImage string, retryCount, parallelCount int, refreshRate, backupInterval string) (err error) { +func Start(buildInfo utils.BuildInfo, o orchestrators.Orchestrator, s Server, volumeFilters volume.Filters, providersFile, targetURL, logServer, agentImage string, retryCount, parallelCount int, refreshRate, backupInterval string, backupTimeSpec string) (err error) { p, err := LoadProviders(providersFile) if err != nil { err = fmt.Errorf("failed to read providers file: %s", err) @@ -47,6 +47,18 @@ func Start(buildInfo utils.BuildInfo, o orchestrators.Orchestrator, s Server, vo return } + //check backupTimeSpec format and constraint 00h < xxh < 23h e.g: 23h59m does not respect constraint + backupTime, err := time.ParseDuration(backupTimeSpec) + if err != nil { + err = fmt.Errorf("failed to parse backup prefered time: %s", err) + return + } + + if backupTime.Hours() >= 23 && backupTime.Hours() >= 0 { + err = fmt.Errorf("backup prefered time does not respect constraint (00h < x < 23h): %s", err) + return + } + backupInt, err := time.ParseDuration(backupInterval) if err != nil { err = fmt.Errorf("failed to parse backup interval: %s", err) @@ -90,7 +102,7 @@ func Start(buildInfo utils.BuildInfo, o orchestrators.Orchestrator, s Server, vo delete(orphanAgents, val) } - if !isBackupNeeded(v, backupInt) { + if !isBackupNeeded(v, backupInt, backupTime) { continue } @@ -179,7 +191,7 @@ func Start(buildInfo utils.BuildInfo, o orchestrators.Orchestrator, s Server, vo return } -func isBackupNeeded(v *volume.Volume, backupInt time.Duration) bool { +func isBackupNeeded(v *volume.Volume, backupInt time.Duration,backupTime time.Duration) bool { if v.BackingUp { return false } @@ -204,11 +216,25 @@ func isBackupNeeded(v *volume.Volume, backupInt time.Duration) bool { return false } + // retry if last backup status is Failed if lbd.Add(time.Hour).Before(time.Now().UTC()) && v.LastBackupStatus == "Failed" { return true } - if lbd.Add(backupInt).Before(time.Now().UTC()) { + // convert targetBackupTime in date with today time + now := time.Now().UTC() + targetBackupTime := time.Date(now.Year(), now.Month(), now.Day(),0,0,0,0,now.Location()).Add(backupTime) + + // set backup interval to 23h if backupTime is not set to default value + if backupTime.Seconds() > 0 && targetBackupTime.Before(now) { + fixInterval, _ := time.ParseDuration("23h") + if lbd.Add(fixInterval).Before(now) { + return true + } + } + + // if backupTime is set to default value + if lbd.Add(backupInt).Before(now) && backupTime.Seconds() == 0 { return true } return false diff --git a/internal/manager/manager_test.go b/internal/manager/manager_test.go index bb5de6e1..13c962c7 100644 --- a/internal/manager/manager_test.go +++ b/internal/manager/manager_test.go @@ -20,9 +20,10 @@ func TestIsBackupNeededBackupIntervalStatusSuccess(t *testing.T) { } h, _ := time.ParseDuration("30m") - assert.Equal(t, isBackupNeeded(givenVolume, h), true) + d, _ := time.ParseDuration("0s") // default prefered time + assert.Equal(t, isBackupNeeded(givenVolume, h,d), true) h, _ = time.ParseDuration("12h") - assert.Equal(t, isBackupNeeded(givenVolume, h), false) + assert.Equal(t, isBackupNeeded(givenVolume, h,d), false) } func TestIsBackupNeededBackupIntervalStatusFailed(t *testing.T) { @@ -35,7 +36,8 @@ func TestIsBackupNeededBackupIntervalStatusFailed(t *testing.T) { } h, _ := time.ParseDuration("30m") - assert.Equal(t, isBackupNeeded(givenVolume, h), true) + d, _ := time.ParseDuration("0s") // default prefered time + assert.Equal(t, isBackupNeeded(givenVolume, h,d), true) h, _ = time.ParseDuration("12h") - assert.Equal(t, isBackupNeeded(givenVolume, h), true) + assert.Equal(t, isBackupNeeded(givenVolume, h,d), true) } diff --git a/pkg/orchestrators/kubernetes.go b/pkg/orchestrators/kubernetes.go index a47c4a23..c0d339e6 100644 --- a/pkg/orchestrators/kubernetes.go +++ b/pkg/orchestrators/kubernetes.go @@ -24,6 +24,7 @@ import ( type KubernetesConfig struct { Namespace string AllNamespaces bool + CustomNamespaces string KubeConfig string AgentServiceAccount string AgentLabelsInline string @@ -556,6 +557,20 @@ func (o *KubernetesOrchestrator) getNamespaces() (namespaces []string, err error for _, namespace := range nms.Items { namespaces = append(namespaces, namespace.Name) } + } else if o.config.CustomNamespaces != "" { + nms, err := o.client.CoreV1().Namespaces().List(metav1.ListOptions{}) + if err != nil { + err = fmt.Errorf("failed to retrieve the list of namespaces: %s", err) + return []string{}, err + } + customNamespaces := strings.Split(o.config.CustomNamespaces, ",") + for _, namespace := range nms.Items { + for _, ns := range customNamespaces { + if ns == namespace.Name { + namespaces = append(namespaces, namespace.Name) + } + } + } } else { namespaces = append(namespaces, o.config.Namespace) }