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

Add functionality to create RepositoryServer CR using kanctl command line #2032

Merged
merged 41 commits into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
9fdf049
Add repository-server in kanctl create
r4rajat Apr 24, 2023
8f33915
Merge branch 'master' into repository-server-create-kanctl
r4rajat May 11, 2023
c782588
Add docs
r4rajat May 11, 2023
ef2b32f
Merge branch 'master' into repository-server-create-kanctl
r4rajat May 12, 2023
92ead2b
Change Flag values
r4rajat May 12, 2023
62f84c8
Update docs
r4rajat May 12, 2023
d160130
Add s3compliant storage option for kopia repository server
r4rajat May 15, 2023
08f96d9
Merge branch 'master' into repository-server-create-kanctl
r4rajat May 16, 2023
0628e3f
Address Comments
r4rajat May 16, 2023
3f3d96c
Remove support for using namespace/name
r4rajat May 16, 2023
99372fc
Addressed Comments
r4rajat May 16, 2023
6cf5846
Addressed Comments
r4rajat May 16, 2023
a59bdb2
Updated Documentation
r4rajat May 16, 2023
8763400
Address Comments
r4rajat May 17, 2023
5d2a906
Error handling for clients initialization
r4rajat May 17, 2023
722ddda
Merge branch 'master' into repository-server-create-kanctl
r4rajat May 19, 2023
4be1bbd
Update pkg/kanctl/repositoryserver.go
r4rajat May 22, 2023
f66625c
Update documentation
r4rajat May 22, 2023
42dcbc9
Address comments
r4rajat May 22, 2023
4541829
Add wait feature
r4rajat May 22, 2023
fbf6fa4
Merge branch 'master' into repository-server-create-kanctl
r4rajat May 23, 2023
e569616
Dynamic kanister namespace
r4rajat May 23, 2023
b172a2a
Merge remote-tracking branch 'origin/repository-server-create-kanctl'…
r4rajat May 23, 2023
0f16558
Merge branch 'master' into repository-server-create-kanctl
r4rajat May 24, 2023
e3eab6d
Merge branch 'master' into repository-server-create-kanctl
r4rajat May 25, 2023
614526d
Update pkg/kanctl/repositoryserver.go
r4rajat May 25, 2023
f95fa8e
Update pkg/kanctl/repositoryserver.go
r4rajat May 25, 2023
d14f623
Update pkg/kanctl/repositoryserver.go
r4rajat May 25, 2023
5a110d2
Update documentation
r4rajat May 25, 2023
9a35bf7
Merge branch 'master' into repository-server-create-kanctl
r4rajat May 25, 2023
e931f35
Fix Lint Issue
r4rajat May 25, 2023
ba23554
Address Comment
r4rajat May 25, 2023
e848f07
Add descriptive output in case repo server creation fails with --wait
r4rajat May 26, 2023
8b0adde
Merge branch 'master' into repository-server-create-kanctl
r4rajat May 26, 2023
bac4a77
Fix Lint Issues
r4rajat May 26, 2023
4f13d08
Update flags
r4rajat May 26, 2023
7582bc5
Update flags
r4rajat May 26, 2023
5dd2500
Update Documentation
r4rajat May 26, 2023
7ba5470
Revert go.sum
r4rajat May 27, 2023
e05dfc0
Revert go.sum
r4rajat May 27, 2023
67cee75
Add kopia in flags
r4rajat May 27, 2023
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
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:
-h, --help help for repository-server
-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
-r, --repository-password-secret string name of the secret containing password for the kopia repository
-a, --repository-server-admin-user-access-secret string name of the secret having admin credentials to connect to connect to kopia repository server
-u, --repository-server-user string name of the user to be created for the kopia repository server
-k, --repository-server-user-access-secret string name of the secret having access credentials of the users that can connect to kopia repository server
-z, --repository-user string name of the user for accessing the kopia repository
-t, --tls-secret string name of the tls secret needed for secure kopia client and kopia repository server communication
r4rajat marked this conversation as resolved.
Show resolved Hide resolved
-w, --wait wait for the kopia repository server CR to be in ready state after creation

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
292 changes: 292 additions & 0 deletions pkg/kanctl/repositoryserver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
// 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 = "repository-server-user"
repoServerUserAccessSecretFlag = "repository-server-user-access-secret"
repoServerAdminUserAccessSecretFlag = "repository-server-admin-user-access-secret"
repoPasswordSecretFlag = "repository-password-secret"
repoUserFlag = "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, "k", "", "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(repoPasswordSecretFlag, "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(repoUserFlag, "z", "", "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")

r4rajat marked this conversation as resolved.
Show resolved Hide resolved
_ = cmd.MarkFlagRequired(tlsSecretFlag)
_ = cmd.MarkFlagRequired(repoServerUserFlag)
r4rajat marked this conversation as resolved.
Show resolved Hide resolved
_ = cmd.MarkFlagRequired(repoServerUserAccessSecretFlag)
_ = cmd.MarkFlagRequired(repoServerAdminUserAccessSecretFlag)
_ = cmd.MarkFlagRequired(repoPasswordSecretFlag)
_ = 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 {
r4rajat marked this conversation as resolved.
Show resolved Hide resolved
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
}

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, 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)
r4rajat marked this conversation as resolved.
Show resolved Hide resolved
if strings.Contains(tlsSecret, "/") {
r4rajat marked this conversation as resolved.
Show resolved Hide resolved
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(repoUserFlag)

repositoryPassword, _ := cmd.Flags().GetString(repoPasswordSecretFlag)
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
r4rajat marked this conversation as resolved.
Show resolved Hide resolved
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 *versioned.Clientset, rs *v1alpha1.RepositoryServer) error {
timeoutCtx, waitCancel := context.WithTimeout(ctx, contextWaitTimeout)
defer waitCancel()
err := poll.Wait(timeoutCtx, func(ctx context.Context) (bool, error) {
repositoryServer, err := cli.CrV1alpha1().RepositoryServers(rs.GetNamespace()).Get(ctx, rs.GetName(), metav1.GetOptions{})
if repositoryServer.Status.Progress == v1alpha1.ServerReady && err == nil {
return true, nil
}
return false, nil
r4rajat marked this conversation as resolved.
Show resolved Hide resolved
})
if err != nil {
return err
}
return nil
r4rajat marked this conversation as resolved.
Show resolved Hide resolved
}