Skip to content

Commit

Permalink
Add functionality to create RepositoryServer CR using kanctl command …
Browse files Browse the repository at this point in the history
…line (#2032)

* Add repository-server in kanctl create

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Add docs

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Change Flag values

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Update docs

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Add s3compliant storage option for kopia repository server

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Address Comments

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Remove support for using namespace/name

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Addressed Comments

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Addressed Comments

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Updated Documentation

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Address Comments

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Error handling for clients initialization

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Update pkg/kanctl/repositoryserver.go

Co-authored-by: Ankit Jain <ankitjain.meone@gmail.com>

* Update documentation

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Address comments

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Add wait feature

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Dynamic kanister namespace

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Update pkg/kanctl/repositoryserver.go

Co-authored-by: Vivek Singh <vsingh.ggits.2010@gmail.com>

* Update pkg/kanctl/repositoryserver.go

Co-authored-by: Vivek Singh <vsingh.ggits.2010@gmail.com>

* Update pkg/kanctl/repositoryserver.go

Co-authored-by: Vivek Singh <vsingh.ggits.2010@gmail.com>

* Update documentation

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Fix Lint Issue

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Address Comment

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Add descriptive output in case repo server creation fails with --wait

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Fix Lint Issues

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Update flags

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Update flags

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Update Documentation

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Revert go.sum

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Revert go.sum

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Add kopia in flags

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

---------

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>
Co-authored-by: Ankit Jain <ankitjain.meone@gmail.com>
Co-authored-by: Vivek Singh <vsingh.ggits.2010@gmail.com>
  • Loading branch information
3 people committed May 29, 2023
1 parent f88b9fa commit fd4624a
Show file tree
Hide file tree
Showing 3 changed files with 340 additions and 0 deletions.
32 changes: 32 additions & 0 deletions docs/tooling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ kanctl create
Available Commands:
actionset Create a new ActionSet or override a <parent> ActionSet
profile Create a new profile
repository-server Create a new kopia repository server
Flags:
--dry-run if set, resource YAML will be printed but not created
Expand Down Expand Up @@ -229,6 +230,37 @@ A new S3Compliant profile can be created using the s3compliant subcommand
secret 's3-secret-chst2' created
profile 's3-profile-5mmkj' created
Kopia Repository Server resource creation using ``kanctl create``
.. code-block:: bash
$ kanctl create repository-server --help
Create a new RepositoryServer
Usage:
kanctl create repository-server [flags]
Flags:
-a, --admin-user-access-secret string name of the secret having admin credentials to connect to connect to kopia repository server
-r, --kopia-repository-password-secret string name of the secret containing password for the kopia repository
-k, --kopia-repository-user string name of the user for accessing the kopia repository
-c, --location-creds-secret string name of the secret containing kopia repository storage credentials
-l, --location-secret string name of the secret containing kopia repository storage location details
-p, --prefix string prefix to be set in kopia repository
-t, --tls-secret string name of the tls secret needed for secure kopia client and kopia repository server communication
-u, --user string name of the user to be created for the kopia repository server
-s, --user-access-secret string name of the secret having access credentials of the users that can connect to kopia repository server
-w, --wait wait for the kopia repository server to be in ready state after creation
-h, --help help for repository-server
Global Flags:
--dry-run if set, resource YAML will be printed but not created
-n, --namespace string Override namespace obtained from kubectl context
--skip-validation if set, resource is not validated before creation
--verbose Display verbose output
.. _kanctlvalidate:
kanctl validate
Expand Down
1 change: 1 addition & 0 deletions pkg/kanctl/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func newCreateCommand() *cobra.Command {
}
cmd.AddCommand(newActionSetCmd())
cmd.AddCommand(newProfileCommand())
cmd.AddCommand(newRepositoryServerCommand())
cmd.PersistentFlags().Bool(dryRunFlag, false, "if set, resource YAML will be printed but not created")
cmd.PersistentFlags().Bool(skipValidationFlag, false, "if set, resource is not validated before creation")
return cmd
Expand Down
307 changes: 307 additions & 0 deletions pkg/kanctl/repositoryserver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
// Copyright 2023 The Kanister Authors.
//
// 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 kanctl

import (
"context"
"fmt"
"strings"
"time"

"github.com/pkg/errors"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"

"github.com/kanisterio/kanister/pkg/apis/cr/v1alpha1"
"github.com/kanisterio/kanister/pkg/client/clientset/versioned"
"github.com/kanisterio/kanister/pkg/kube"
"github.com/kanisterio/kanister/pkg/poll"
)

const (
tlsSecretFlag = "tls-secret"
repoServerUserFlag = "user"
repoServerUserAccessSecretFlag = "user-access-secret"
repoServerAdminUserAccessSecretFlag = "admin-user-access-secret"
kopiaRepoPasswordSecretFlag = "kopia-repository-password-secret"
kopiaRepoUserFlag = "kopia-repository-user"
locationCredsSecretFlag = "location-creds-secret"
locationSecretFlag = "location-secret"
defaultRepositoryServerHost = "localhost"
waitFlag = "wait"
contextWaitTimeout = 10 * time.Minute
)

type repositoryServerParams struct {
tls string
repositoryServerUser string
repositoryServerUserAccess string
repositoryServerAdminUserAccess string
repositoryUser string
repositoryPassword string
prefix string
location string
locationCreds string
namespace string
}

func newRepositoryServerCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "repository-server",
Short: "Create a new RepositoryServer",
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
return createNewRepositoryServer(cmd, args)
},
}

cmd.PersistentFlags().StringP(tlsSecretFlag, "t", "", "name of the tls secret needed for secure kopia client and kopia repository server communication")
cmd.PersistentFlags().StringP(repoServerUserFlag, "u", "", "name of the user to be created for the kopia repository server")
cmd.PersistentFlags().StringP(repoServerUserAccessSecretFlag, "s", "", "name of the secret having access credentials of the users that can connect to kopia repository server")
cmd.PersistentFlags().StringP(repoServerAdminUserAccessSecretFlag, "a", "", "name of the secret having admin credentials to connect to connect to kopia repository server")
cmd.PersistentFlags().StringP(kopiaRepoPasswordSecretFlag, "r", "", "name of the secret containing password for the kopia repository")
cmd.PersistentFlags().StringP(prefixFlag, "p", "", "prefix to be set in kopia repository")
cmd.PersistentFlags().StringP(kopiaRepoUserFlag, "k", "", "name of the user for accessing the kopia repository")
cmd.PersistentFlags().StringP(locationSecretFlag, "l", "", "name of the secret containing kopia repository storage location details")
cmd.PersistentFlags().StringP(locationCredsSecretFlag, "c", "", "name of the secret containing kopia repository storage credentials")
cmd.PersistentFlags().BoolP(waitFlag, "w", false, "wait for the kopia repository server to be in ready state after creation")

_ = cmd.MarkFlagRequired(tlsSecretFlag)
_ = cmd.MarkFlagRequired(repoServerUserFlag)
_ = cmd.MarkFlagRequired(repoServerUserAccessSecretFlag)
_ = cmd.MarkFlagRequired(repoServerAdminUserAccessSecretFlag)
_ = cmd.MarkFlagRequired(kopiaRepoPasswordSecretFlag)
_ = cmd.MarkFlagRequired(prefixFlag)
_ = cmd.MarkFlagRequired(locationSecretFlag)
_ = cmd.MarkFlagRequired(locationCredsSecretFlag)
return cmd
}

func createNewRepositoryServer(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true
if len(args) > 0 {
return newArgsLengthError("expected 0 args. Got %#v", args)
}

rsParams, err := generateRepositoryServerParams(cmd)
if err != nil {
return err
}

repositoryServer, err := validateSecretsAndConstructRepositoryServer(rsParams)
if err != nil {
return err
}

config, err := kube.LoadConfig()
if err != nil {
return err
}
cli, err := kubernetes.NewForConfig(config)
if err != nil {
return errors.Wrap(err, "could not get the kubernetes client")
}
crCli, err := versioned.NewForConfig(config)
if err != nil {
return errors.Wrap(err, "could not get the CRD client")
}

ctx := context.Background()
rs, err := crCli.CrV1alpha1().RepositoryServers(rsParams.namespace).Create(ctx, repositoryServer, metav1.CreateOptions{})
if err != nil {
return err
}
fmt.Printf("repositoryservers.cr.kanister.io/%s created\n", rs.GetName())

waitFlag, _ := cmd.Flags().GetBool(waitFlag)
if waitFlag {
fmt.Print("Waiting for the kopia repository server CR to be in ready state...\n")
err = waitForRepositoryServerReady(ctx, cli, crCli, rs)
if err != nil {
return err
}
fmt.Printf("repositoryservers.cr.kanister.io/%s is ready.\n", rs.GetName())
}
return nil
}

func generateRepositoryServerParams(cmd *cobra.Command) (*repositoryServerParams, error) {
// Fetch values of the flags
tlsSecret, _ := cmd.Flags().GetString(tlsSecretFlag)
if strings.Contains(tlsSecret, "/") {
return nil, errors.Errorf("Invalid secret name %s, it should not be of the form namespace/name )", tlsSecret)
}

repositoryServerUser, _ := cmd.Flags().GetString(repoServerUserFlag)

repositoryServerUserAccessSecret, _ := cmd.Flags().GetString(repoServerUserAccessSecretFlag)
if strings.Contains(repositoryServerUserAccessSecret, "/") {
return nil, errors.Errorf("Invalid secret name %s, it should not be of the form namespace/name )", repositoryServerUserAccessSecret)
}

repositoryServerAdminUserAccessSecret, _ := cmd.Flags().GetString(repoServerAdminUserAccessSecretFlag)
if strings.Contains(repositoryServerAdminUserAccessSecret, "/") {
return nil, errors.Errorf("Invalid secret name %s, it should not be of the form namespace/name )", repositoryServerAdminUserAccessSecret)
}

repositoryUser, _ := cmd.Flags().GetString(kopiaRepoUserFlag)

repositoryPassword, _ := cmd.Flags().GetString(kopiaRepoPasswordSecretFlag)
if strings.Contains(repositoryPassword, "/") {
return nil, errors.Errorf("Invalid secret name %s, it should not be of the form namespace/name )", repositoryPassword)
}

prefix, _ := cmd.Flags().GetString(prefixFlag)

location, _ := cmd.Flags().GetString(locationSecretFlag)
if strings.Contains(location, "/") {
return nil, errors.Errorf("Invalid secret name %s, it should not be of the form namespace/name )", location)
}

locationCreds, _ := cmd.Flags().GetString(locationCredsSecretFlag)
if strings.Contains(locationCreds, "/") {
return nil, errors.Errorf("Invalid secret name %s, it should not be of the form namespace/name )", locationCreds)
}

ns, err := resolveNamespace(cmd)
if err != nil {
return nil, err
}

return &repositoryServerParams{
tls: tlsSecret,
repositoryServerUser: repositoryServerUser,
repositoryServerUserAccess: repositoryServerUserAccessSecret,
repositoryUser: repositoryUser,
repositoryServerAdminUserAccess: repositoryServerAdminUserAccessSecret,
repositoryPassword: repositoryPassword,
prefix: prefix,
location: location,
locationCreds: locationCreds,
namespace: ns,
}, nil
}

func validateSecretsAndConstructRepositoryServer(rsParams *repositoryServerParams) (*v1alpha1.RepositoryServer, error) {
// Fetch and Validate Secrets
ctx := context.Background()
config, err := kube.LoadConfig()
if err != nil {
return nil, err
}
cli, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, errors.Wrap(err, "could not get the kubernetes client")
}
tlsSecret, err := cli.CoreV1().Secrets(rsParams.namespace).Get(ctx, rsParams.tls, metav1.GetOptions{})
if err != nil {
return nil, err
}
repositoryServerUserAccessSecret, err := cli.CoreV1().Secrets(rsParams.namespace).Get(ctx, rsParams.repositoryServerUserAccess, metav1.GetOptions{})
if err != nil {
return nil, err
}
repositoryServerAdminUserAccessSecret, err := cli.CoreV1().Secrets(rsParams.namespace).Get(ctx, rsParams.repositoryServerAdminUserAccess, metav1.GetOptions{})
if err != nil {
return nil, err
}
repositoryPasswordSecret, err := cli.CoreV1().Secrets(rsParams.namespace).Get(ctx, rsParams.repositoryPassword, metav1.GetOptions{})
if err != nil {
return nil, err
}
locationSecret, err := cli.CoreV1().Secrets(rsParams.namespace).Get(ctx, rsParams.location, metav1.GetOptions{})
if err != nil {
return nil, err
}
locationCredsSecret, err := cli.CoreV1().Secrets(rsParams.namespace).Get(ctx, rsParams.locationCreds, metav1.GetOptions{})
if err != nil {
return nil, err
}

return &v1alpha1.RepositoryServer{
ObjectMeta: metav1.ObjectMeta{
GenerateName: `kopia-repo-server-`,
},
Spec: v1alpha1.RepositoryServerSpec{
Storage: v1alpha1.Storage{
SecretRef: corev1.SecretReference{
Name: locationSecret.GetName(),
Namespace: locationSecret.GetNamespace(),
},
CredentialSecretRef: corev1.SecretReference{
Name: locationCredsSecret.GetName(),
Namespace: locationCredsSecret.GetNamespace(),
},
},
Repository: v1alpha1.Repository{
RootPath: rsParams.prefix,
Username: rsParams.repositoryUser,
Hostname: defaultRepositoryServerHost,
PasswordSecretRef: corev1.SecretReference{
Name: repositoryPasswordSecret.GetName(),
Namespace: repositoryPasswordSecret.GetNamespace(),
},
},
Server: v1alpha1.Server{
UserAccess: v1alpha1.UserAccess{
UserAccessSecretRef: corev1.SecretReference{
Name: repositoryServerUserAccessSecret.GetName(),
Namespace: repositoryServerUserAccessSecret.GetNamespace(),
},
Username: rsParams.repositoryServerUser,
},
AdminSecretRef: corev1.SecretReference{
Name: repositoryServerAdminUserAccessSecret.GetName(),
Namespace: repositoryServerAdminUserAccessSecret.GetNamespace(),
},
TLSSecretRef: corev1.SecretReference{
Name: tlsSecret.GetName(),
Namespace: tlsSecret.GetNamespace(),
},
},
},
}, nil
}

func waitForRepositoryServerReady(ctx context.Context, cli *kubernetes.Clientset, crCli *versioned.Clientset, rs *v1alpha1.RepositoryServer) error {
timeoutCtx, waitCancel := context.WithTimeout(ctx, contextWaitTimeout)
defer waitCancel()
pollErr := poll.Wait(timeoutCtx, func(ctx context.Context) (bool, error) {
repositoryServer, err := crCli.CrV1alpha1().RepositoryServers(rs.GetNamespace()).Get(ctx, rs.GetName(), metav1.GetOptions{})
if repositoryServer.Status.Progress == v1alpha1.ServerReady && err == nil {
return true, nil
}
return false, err
})
if pollErr != nil {
repositoryServer, err := crCli.CrV1alpha1().RepositoryServers(rs.GetNamespace()).Get(ctx, rs.GetName(), metav1.GetOptions{})
if err != nil {
return errors.Wrapf(err, "Error Getting repository server %s", repositoryServer.GetName())
}

opts := metav1.ListOptions{
FieldSelector: fmt.Sprintf("involvedObject.name=%s", repositoryServer.GetName()),
}
events, err := cli.CoreV1().Events(repositoryServer.GetNamespace()).List(ctx, opts)
if err != nil {
return err
}

return errors.Wrapf(pollErr, "Repository Server is not ready.\nCurrent Status: %s\nReason: %s\n", repositoryServer.Status.Progress, events.Items[0].Message)
}
return nil
}

0 comments on commit fd4624a

Please sign in to comment.