Skip to content

Commit

Permalink
Add nats Stash addon (#2)
Browse files Browse the repository at this point in the history
* Add NATS Stash Addon

Signed-off-by: hmsayem <hmsayem@appscode.com>

* Add TLS Authentication

Signed-off-by: hmsayem <hmsayem@appscode.com>

* Add Token Authentication

Signed-off-by: hmsayem <hmsayem@appscode.com>

* Add JWT Authentication

Signed-off-by: hmsayem <hmsayem@appscode.com>

* Update TLS

Signed-off-by: hmsayem <hmsayem@appscode.com>

* Add Nkey authentication

Signed-off-by: hmsayem <hmsayem@appscode.com>

* Add client certificate authentication

Signed-off-by: hmsayem <hmsayem@appscode.com>

* Update code

Signed-off-by: hmsayem <hmsayem@appscode.com>

* Add overwrite flag

Signed-off-by: hmsayem <hmsayem@appscode.com>

* Update args

Signed-off-by: hmsayem <hmsayem@appscode.com>

* Update flag usage

Signed-off-by: hmsayem <hmsayem@appscode.com>
  • Loading branch information
hmsayem authored Sep 1, 2021
1 parent 48941bd commit 4e97bdc
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 89 deletions.
12 changes: 7 additions & 5 deletions Dockerfile.dbg
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.

FROM debian:bullseye
FROM alpine

ENV DEBIAN_FRONTEND noninteractive
ENV DEBCONF_NONINTERACTIVE_SEEN true

RUN set -x \
&& apt-get update \
&& apt-get install -y --no-install-recommends apt-transport-https ca-certificates curl bzip2 tar
&& apk update \
&& apk add ca-certificates curl bzip2 tar

RUN set -x \
&& curl -fsSL -o restic.bz2 https://github.com/stashed/restic/releases/download/v{RESTIC_VER}/restic_{RESTIC_VER}_{ARG_OS}_{ARG_ARCH}.bz2 \
Expand All @@ -35,11 +35,13 @@ ENV DEBIAN_FRONTEND noninteractive
ENV DEBCONF_NONINTERACTIVE_SEEN true

RUN set -x \
&& apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& apk update \
&& apk add ca-certificates \
&& rm -rf /var/lib/apt/lists/* /usr/share/doc /usr/share/man /tmp/*

COPY --from=0 restic /bin/restic
COPY bin/{ARG_OS}_{ARG_ARCH}/{ARG_BIN} /{ARG_BIN}

USER nobody

ENTRYPOINT ["/{ARG_BIN}"]
10 changes: 5 additions & 5 deletions Dockerfile.in
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.

FROM debian:bullseye
FROM alpine

ENV DEBIAN_FRONTEND noninteractive
ENV DEBCONF_NONINTERACTIVE_SEEN true

RUN set -x \
&& apt-get update \
&& apt-get install -y --no-install-recommends apt-transport-https ca-certificates curl bzip2 tar
&& apk update \
&& apk add --no-cache ca-certificates curl bzip2 tar

RUN set -x \
&& curl -fsSL -o restic.bz2 https://github.com/stashed/restic/releases/download/v{RESTIC_VER}/restic_{RESTIC_VER}_{ARG_OS}_{ARG_ARCH}.bz2 \
Expand All @@ -34,8 +34,8 @@ ENV DEBIAN_FRONTEND noninteractive
ENV DEBCONF_NONINTERACTIVE_SEEN true

RUN set -x \
&& apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& apk update \
&& apk add ca-certificates \
&& rm -rf /var/lib/apt/lists/* /usr/share/doc /usr/share/man /tmp/*

COPY --from=0 /restic /bin/restic
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ BIN_PLATFORMS := $(DOCKER_PLATFORMS)
OS := $(if $(GOOS),$(GOOS),$(shell go env GOOS))
ARCH := $(if $(GOARCH),$(GOARCH),$(shell go env GOARCH))

BASEIMAGE_PROD ?= natsio/natsbox:0.6.0
BASEIMAGE_DBG ?= natsio/natsbox:0.6.0
BASEIMAGE_PROD ?= natsio/nats-box:0.6.0
BASEIMAGE_DBG ?= natsio/nats-box:0.6.0

IMAGE := $(REGISTRY)/$(BIN)
VERSION_PROD := $(VERSION)
Expand Down
File renamed without changes.
File renamed without changes.
111 changes: 82 additions & 29 deletions pkg/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ package pkg

import (
"context"
"encoding/json"
"io/ioutil"
"path/filepath"
"strconv"
"strings"

api_v1beta1 "stash.appscode.dev/apimachinery/apis/stash/v1beta1"
stash "stash.appscode.dev/apimachinery/client/clientset/versioned"
Expand All @@ -33,6 +33,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/klog/v2"
appcatalog "kmodules.xyz/custom-resources/apis/appcatalog/v1alpha1"
appcatalog_cs "kmodules.xyz/custom-resources/client/clientset/versioned"
v1 "kmodules.xyz/offshoot-api/api/v1"
Expand All @@ -49,8 +50,7 @@ func NewCmdBackup() *cobra.Command {
EnableCache: false,
},
backupOptions: restic.BackupOptions{
Host: restic.DefaultHost,
StdinFileName: NATSDumpFile,
Host: restic.DefaultHost,
},
}
)
Expand Down Expand Up @@ -110,15 +110,14 @@ func NewCmdBackup() *cobra.Command {
}

return nil

},
}

cmd.Flags().StringVar(&opt.natsArgs, "nats-args", opt.natsArgs, "Additional arguments")
cmd.Flags().Int32Var(&opt.waitTimeout, "wait-timeout", opt.waitTimeout, "Time limit to wait for the database to be ready")

cmd.Flags().StringVar(&masterURL, "master", masterURL, "The address of the Kubernetes API server (overrides any value in kubeconfig)")
cmd.Flags().StringVar(&kubeconfigPath, "kubeconfig", kubeconfigPath, "Path to kubeconfig file with authorization information (the master location is set by the master flag).")
cmd.Flags().StringVar(&kubeconfigPath, "kubeconfig", kubeconfigPath, "Path to kubeconfig file with authorization information (the master location is set by the master flag)")
cmd.Flags().StringVar(&opt.namespace, "namespace", "default", "Namespace of Backup/Restore Session")
cmd.Flags().StringVar(&opt.backupSessionName, "backupsession", opt.backupSessionName, "Name of the Backup Session")
cmd.Flags().StringVar(&opt.appBindingName, "appbinding", opt.appBindingName, "Name of the app binding")
Expand All @@ -134,7 +133,6 @@ func NewCmdBackup() *cobra.Command {
cmd.Flags().Int64Var(&opt.setupOptions.MaxConnections, "max-connections", opt.setupOptions.MaxConnections, "Specify maximum concurrent connections for GCS, Azure and B2 backend")

cmd.Flags().StringVar(&opt.backupOptions.Host, "hostname", opt.backupOptions.Host, "Name of the host machine")

cmd.Flags().Int64Var(&opt.backupOptions.RetentionPolicy.KeepLast, "retention-keep-last", opt.backupOptions.RetentionPolicy.KeepLast, "Specify value for retention strategy")
cmd.Flags().Int64Var(&opt.backupOptions.RetentionPolicy.KeepHourly, "retention-keep-hourly", opt.backupOptions.RetentionPolicy.KeepHourly, "Specify value for retention strategy")
cmd.Flags().Int64Var(&opt.backupOptions.RetentionPolicy.KeepDaily, "retention-keep-daily", opt.backupOptions.RetentionPolicy.KeepDaily, "Specify value for retention strategy")
Expand All @@ -145,8 +143,9 @@ func NewCmdBackup() *cobra.Command {
cmd.Flags().BoolVar(&opt.backupOptions.RetentionPolicy.Prune, "retention-prune", opt.backupOptions.RetentionPolicy.Prune, "Specify whether to prune old snapshot data")
cmd.Flags().BoolVar(&opt.backupOptions.RetentionPolicy.DryRun, "retention-dry-run", opt.backupOptions.RetentionPolicy.DryRun, "Specify whether to test retention policy without deleting actual data")

cmd.Flags().StringVar(&opt.interimDataDir, "interim-data-dir", opt.interimDataDir, "Directory where the targeted data will be stored temporarily before uploading to the backend")
cmd.Flags().StringVar(&opt.outputDir, "output-dir", opt.outputDir, "Directory where output.json file will be written (keep empty if you don't need to write output in file)")

cmd.Flags().StringSliceVar(&opt.streams, "streams", opt.streams, "List of streams to backup. Keep empty to backup all streams")
return cmd
}

Expand Down Expand Up @@ -177,50 +176,104 @@ func (opt *natsOptions) backupNATS(targetRef api_v1beta1.TargetRef) (*restic.Bac
if err != nil {
return nil, err
}

// get app binding
appBinding, err := opt.catalogClient.AppcatalogV1alpha1().AppBindings(opt.namespace).Get(context.TODO(), opt.appBindingName, metav1.GetOptions{})
if err != nil {
return nil, err
}

// init restic wrapper
resticWrapper, err := restic.NewResticWrapper(opt.setupOptions)
// clear directory
klog.Infoln("Cleaning up temporary data directory directory: ", opt.interimDataDir)
if err := clearDir(opt.interimDataDir); err != nil {
return nil, err
}
// wait for NATS ready
err = opt.waitForNATSReady(appBinding)
if err != nil {
return nil, err
}

// run separate shell to perform backup
backupShell := NewSessionWrapper()
backupShell.ShowCMD = true
// set access credentials
err = opt.setCredentials(resticWrapper, appBinding)
err = opt.setCredentials(backupShell, appBinding)
if err != nil {
return nil, err
}

// setup pipe command
backupCmd := restic.Command{
Name: NATSBackupCMD,
Args: []interface{}{
"-host", appBinding.Spec.ClientConfig.Service.Name,
},
}
for _, arg := range strings.Fields(opt.natsArgs) {
backupCmd.Args = append(backupCmd.Args, arg)
// set TLS
err = opt.setTLS(backupShell, appBinding)
if err != nil {
return nil, err
}

// if port is specified, append port in the arguments
if appBinding.Spec.ClientConfig.Service.Port != 0 {
backupCmd.Args = append(backupCmd.Args, "-port", strconv.Itoa(int(appBinding.Spec.ClientConfig.Service.Port)))
backupArgs := []interface{}{
"stream",
"backup",
"--server", appBinding.Spec.ClientConfig.Service.Name,
}

// wait for DB ready
err = opt.waitForDBReady(appBinding)
streams, err := opt.getStreams(backupShell, appBinding)
if err != nil {
return nil, err
}

// add backup command in the pipeline
opt.backupOptions.StdinPipeCommands = append(opt.backupOptions.StdinPipeCommands, backupCmd)
for i := range streams {
args := append(backupArgs, streams[i], filepath.Join(opt.interimDataDir, streams[i]))
backupShell.Command(NATSCMD, args...)
if err := backupShell.Run(); err != nil {
return nil, err
}
}

// data snapshot has been stored in the interim data dir. Now, we will backup this directory using Stash.
opt.backupOptions.BackupPaths = []string{opt.interimDataDir}

// init restic wrapper
resticWrapper, err := restic.NewResticWrapper(opt.setupOptions)
if err != nil {
return nil, err
}

// Run backup
return resticWrapper.RunBackup(opt.backupOptions, targetRef)
}

func (opt *natsOptions) getStreams(sh *SessionWrapper, appBinding *appcatalog.AppBinding) ([]string, error) {
if len(opt.streams) == 0 {
streamArgs := []interface{}{
"stream",
"ls",
"--json",
"--server", appBinding.Spec.ClientConfig.Service.Name,
}

sh.Command(NATSCMD, streamArgs...)
err := sh.WriteStdout(filepath.Join(opt.interimDataDir, NATSStreamsFile))
if err != nil {
return nil, err
}
byteStreams, err := ioutil.ReadFile(filepath.Join(opt.interimDataDir, NATSStreamsFile))
if err != nil {
return nil, err
}
var streams []string
err = json.Unmarshal(byteStreams, &streams)
if err != nil {
return nil, err
}
return streams, nil

} else {
byteStreams, err := json.Marshal(opt.streams)
if err != nil {
return nil, err
}

err = ioutil.WriteFile(filepath.Join(opt.interimDataDir, NATSStreamsFile), byteStreams, 0644)
if err != nil {
return nil, err
}
return opt.streams, nil
}
}
Loading

0 comments on commit 4e97bdc

Please sign in to comment.