Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example BIA and RIA v2 plugins #66

Merged
merged 1 commit into from
Apr 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
go-version: 1.18

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency this should also be 1.19

id: go
- name: Check out the code
uses: actions/checkout@v2
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

FROM golang:1.17-buster AS build
FROM golang:1.19-bullseye AS build
ENV GOPROXY=https://proxy.golang.org
WORKDIR /go/src/github.com/vmware-tanzu/velero-plugin-example
COPY . .
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ endif
# modules updates Go module files
.PHONY: modules
modules:
go mod tidy
go mod tidy -compat=1.17

# verify-modules ensures Go module files are up to date
.PHONY: verify-modules
Expand Down
54 changes: 39 additions & 15 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,44 +1,68 @@
module github.com/vmware-tanzu/velero-plugin-example

go 1.17
go 1.18

require (
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.8.1
github.com/vmware-tanzu/velero v1.7.1
k8s.io/api v0.22.2
k8s.io/apimachinery v0.22.2
k8s.io/api v0.25.6
k8s.io/apimachinery v0.25.6
k8s.io/client-go v0.25.6
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/go-logr/logr v0.4.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.21.1 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/hashicorp/go-hclog v0.14.1 // indirect
github.com/hashicorp/go-plugin v1.4.3 // indirect
github.com/hashicorp/yamux v0.0.0-20190923154419-df201c70410d // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/json-iterator/go v1.1.11 // indirect
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kopia/kopia v0.10.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/spf13/cobra v1.2.1 // indirect
github.com/spf13/cobra v1.4.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71 // indirect
google.golang.org/grpc v1.40.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb // indirect
google.golang.org/grpc v1.45.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.9.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.70.1 // indirect
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

replace github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.2

replace github.com/vmware-tanzu/velero => github.com/vmware-tanzu/velero v1.10.0-rc.1.0.20230321103129-29b5894be6f1
808 changes: 92 additions & 716 deletions go.sum

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion internal/plugin/backupplugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (p *BackupPlugin) AppliesTo() (velero.ResourceSelector, error) {
// Execute allows the ItemAction to perform arbitrary logic with the item being backed up,
// in this case, setting a custom annotation on the item being backed up.
func (p *BackupPlugin) Execute(item runtime.Unstructured, backup *v1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) {
p.log.Info("Hello from my BackupPlugin!")
p.log.Info("Hello from my BackupPlugin(v1)!")

metadata, err := meta.Accessor(item)
if err != nil {
Expand Down
247 changes: 247 additions & 0 deletions internal/plugin/backuppluginv2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
/*
Copyright the Velero contributors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package plugin

import (
"context"
"strconv"
"strings"
"time"

"github.com/sirupsen/logrus"

corev1api "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/kubernetes"

"github.com/pkg/errors"
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/kuberesource"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2"
)

const (
// If this annotation is found on the Velero Backup CR, then create an operation
// that is considered done at backup start time + example BIA operation duration
// If this annotation is not present, then operationID returned from Execute() will
// be empty.
// This annotation can also be set on the item, which overrides the backup CR value,
// to allow for testing multiple action lengths
AsyncBIADurationAnnotation = "velero.io/example-bia-operation-duration"
// If this annotation is true on the item, then if the BIA duration is set to a
// non-zero value, a Secret will be created, and it will be returned as an additional
// item with the UpdateAdditionalItemsAfterOperation return flag set to true
AsyncBIAAdditionalUpdateAnnotation = "velero.io/example-bia-additional-update"
AsyncBIAProgressAnnotation = "velero.io/example-bia-progress"
AsyncBIAExampleSecretAnnotation = "velero.io/example-bia-secret"
AsyncBIAExampleLabel = "velero.io/example-bia"
)

// BackupPluginV2 is a v2 backup item action plugin for Velero.
type BackupPluginV2 struct {
log logrus.FieldLogger
}

// NewBackupPluginV2 instantiates a v2 BackupPlugin.
func NewBackupPluginV2(log logrus.FieldLogger) *BackupPluginV2 {
return &BackupPluginV2{log: log}
}

// Name is required to implement the interface, but the Velero pod does not delegate this
// method -- it's used to tell velero what name it was registered under. The plugin implementation
// must define it, but it will never actually be called.
func (p *BackupPluginV2) Name() string {
return "exampleBackupPlugin"
}

// AppliesTo returns information about which resources this action should be invoked for.
// The IncludedResources and ExcludedResources slices can include both resources
// and resources with group names. These work: "ingresses", "ingresses.extensions".
// A BackupPlugin's Execute function will only be invoked on items that match the returned
// selector. A zero-valued ResourceSelector matches all resources.
func (p *BackupPluginV2) AppliesTo() (velero.ResourceSelector, error) {
// exclude secrets to avoid infinite loop, since this plugin creates secrets as additional items.
return velero.ResourceSelector{ExcludedResources: []string{"secrets"}}, nil
}

func GetClient() (*kubernetes.Clientset, error) {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
configOverrides := &clientcmd.ConfigOverrides{}
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
clientConfig, err := kubeConfig.ClientConfig()
if err != nil {
return nil, errors.WithStack(err)
}

client, err := kubernetes.NewForConfig(clientConfig)
if err != nil {
return nil, errors.WithStack(err)
}

return client, nil
}

// Execute allows the ItemAction to perform arbitrary logic with the item being backed up,
// in this case, setting a custom annotation on the item being backed up.
func (p *BackupPluginV2) Execute(item runtime.Unstructured, backup *v1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, []velero.ResourceIdentifier, error) {
p.log.Info("Hello from my BackupPlugin(v2)!")

metadata, err := meta.Accessor(item)
if err != nil {
return nil, nil, "", nil, err
}

annotations := metadata.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}

annotations["velero.io/my-backup-pluginv2"] = "1"

metadata.SetAnnotations(annotations)

// Operations during finalize aren't supported, so if backup is in a finalize phase, just return the item
if backup.Status.Phase == v1.BackupPhaseFinalizing ||
backup.Status.Phase == v1.BackupPhaseFinalizingPartiallyFailed {
return item, nil, "", nil, nil
}

operationID := ""
duration := ""
if durationStr, ok := annotations[AsyncBIADurationAnnotation]; ok && len(durationStr) != 0 {
_, err := time.ParseDuration(durationStr)
if err == nil {
duration = durationStr
}
}
if duration == "" && backup.Annotations != nil {
if durationStr, ok := backup.Annotations[AsyncBIADurationAnnotation]; ok && len(durationStr) != 0 {
_, err := time.ParseDuration(durationStr)
if err == nil {
duration = durationStr
}
}
}
// If duration is empty, we don't have an operation so just return the item.
if duration == "" {
return item, nil, "", nil, nil
}

var secret *corev1api.Secret
var itemsToUpdate []velero.ResourceIdentifier
additionalUpdate, ok := annotations[AsyncBIAAdditionalUpdateAnnotation]
if ok && additionalUpdate == "true" {
secret = &corev1api.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: metadata.GetNamespace(),
GenerateName: metadata.GetName() + "-",
Labels: map[string]string{
AsyncBIAExampleLabel: "true",
},
},
Type: corev1api.SecretTypeOpaque,
Data: map[string][]byte{
"TestObject": []byte(metadata.GetName()),
},
}
secretClient, err := GetClient()
if err != nil {
return item, nil, "", nil, errors.Wrap(err, "error getting secret client")
}
if secret, err = secretClient.CoreV1().Secrets(metadata.GetNamespace()).Create(context.TODO(), secret, metav1.CreateOptions{}); err != nil {
return item, nil, "", nil, errors.Wrapf(err, "error creating %s secret", metadata.GetName())
}
}

operationID = string(metadata.GetUID()) + "/" + duration
if secret != nil {
itemsToUpdate = []velero.ResourceIdentifier{
{
GroupResource: kuberesource.Secrets,
Namespace: secret.Namespace,
Name: secret.Name,
},
}
operationID += "/" + secret.Namespace + "/" + secret.Name
annotations[AsyncBIAExampleSecretAnnotation] = secret.Name
metadata.SetAnnotations(annotations)

}
return item, nil, operationID, itemsToUpdate, nil
}

func (p *BackupPluginV2) Progress(operationID string, backup *v1.Backup) (velero.OperationProgress, error) {
progress := velero.OperationProgress{}
if operationID == "" {
return progress, biav2.InvalidOperationIDError(operationID)
}
splitOp := strings.Split(operationID, "/")
if len(splitOp) == 4 {
secretClient, err := GetClient()
if err != nil {
return progress, errors.Wrap(err, "error getting secret client")
}
secret, err := secretClient.CoreV1().Secrets(splitOp[2]).Get(context.TODO(), splitOp[3], metav1.GetOptions{})
if err != nil {
return progress, errors.Wrapf(err, "error getting %s secret", splitOp[3])
}
annotations := secret.Annotations
if annotations == nil {
annotations = make(map[string]string)
}
priorProgressCalls := 0
progressAnnotation, ok := annotations[AsyncBIAProgressAnnotation]
if ok {
i, err := strconv.Atoi(progressAnnotation)
if err == nil {
priorProgressCalls = i
}
}
annotations[AsyncBIAProgressAnnotation] = strconv.Itoa(priorProgressCalls+1)
secret.Annotations = annotations
if _, err := secretClient.CoreV1().Secrets(splitOp[2]).Update(context.TODO(), secret, metav1.UpdateOptions{}); err != nil {
return progress, errors.Wrapf(err, "error updating %s secret", splitOp[3])
}

} else if len(splitOp) != 2 {
return progress, biav2.InvalidOperationIDError(operationID)
}
duration, err := time.ParseDuration(splitOp[1])
if err != nil {
return progress, biav2.InvalidOperationIDError(operationID)
}
elapsed := time.Since(backup.Status.StartTimestamp.Time).Seconds()
if elapsed >= duration.Seconds() {
progress.Completed = true
progress.NCompleted = int64(duration.Seconds())
} else {
progress.NCompleted = int64(elapsed)
}
progress.NTotal = int64(duration.Seconds())
progress.OperationUnits = "seconds"
progress.Updated = time.Now()

return progress, nil
}

func (p *BackupPluginV2) Cancel(operationID string, backup *v1.Backup) error {
return nil
}
Loading