Skip to content

Commit

Permalink
cli: create etcd command
Browse files Browse the repository at this point in the history
This commit introduces the `etcd` comand under `run`.

It is not active yet as it will be in later commits.

Signed-off-by: Elis Lulja <[email protected]>
  • Loading branch information
asimpleidea committed Aug 24, 2022
1 parent dee9e7c commit 3e97414
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 1 deletion.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ require (
sigs.k8s.io/yaml v1.2.0 // indirect
)

require golang.org/x/term v0.0.0-20210927222741-03fcf44c2211

require (
github.com/aws/aws-sdk-go-v2/credentials v1.12.14 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.12 // indirect
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,7 @@ golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJIkPFVM6oDtMFYmgws0=
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
14 changes: 14 additions & 0 deletions pkg/cluster/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,17 @@ func GetFilesFromSecret(ctx context.Context, nsName, name string) ([][]byte, err

return data, nil
}

func GetDataFromSecret(ctx context.Context, nsName, name string) (map[string][]byte, error) {
cli, err := getK8sClientSet()
if err != nil {
return nil, err
}

secret, err := cli.CoreV1().Secrets(nsName).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return nil, err
}

return secret.Data, nil
}
248 changes: 248 additions & 0 deletions pkg/command/run/etcd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
// Copyright © 2022 Cisco
//
// SPDX-License-Identifier: Apache-2.0
//
// 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.
//
// All rights reserved.

package run

import (
"context"
"fmt"
"io/ioutil"
"strings"
"syscall"
"time"

"github.com/CloudNativeSDWAN/cnwan-operator/pkg/cluster"
"github.com/CloudNativeSDWAN/cnwan-operator/pkg/servregistry/etcd"
"github.com/davecgh/go-spew/spew"
"github.com/spf13/cobra"
clientv3 "go.etcd.io/etcd/client/v3"
"golang.org/x/term"
"gopkg.in/yaml.v3"
)

const (
defaultEtcdConfigMapName = "etcd-options"
defaultEtcdCredentialsSecretName = "etcd-credentials"
)

type EtcdOptions struct {
Prefix string `yaml:"prefix"`
Endpoints []string `yaml:"endpoints"`

Username string
Password string
}

func getRunEtcdCommand(operatorOpts *Options) *cobra.Command {
// -----------------------------
// Inits and defaults
// -----------------------------

opts := &EtcdOptions{}
var (
optsPath string
optsConfigMap string
// This is used for the --password flag option, to signal user has
// indeed a password for authentication.
tmpPassFlag bool
credentialsSecret string
)

// -----------------------------
// The command
// -----------------------------

cmd := &cobra.Command{
Use: "etcd [COMMAND] [OPTIONS]",
Short: "Run the program with etcd",
PreRunE: func(cmd *cobra.Command, args []string) error {
l := log.With().Str("cmd", "etcd").Logger()

// -- Get the options from file or ConfigMap
if optsPath != "" || optsConfigMap != "" {
var (
fileOptions []byte
decodedFileOptions *EtcdOptions
)

// -- Get the options from path
if optsPath != "" {
if optsConfigMap != "" {
l.Warn().Msg("both path and configmap flags are provided: only the path will be used")
optsConfigMap = ""
}

log.Debug().Str("path", optsPath).
Msg("getting options from file...")
byteOpts, err := ioutil.ReadFile(optsPath)
if err != nil {
return fmt.Errorf("cannot open file %s: %w", optsPath, err)
}

fileOptions = byteOpts
}

// -- Get options from configmap
if optsConfigMap != "" {
log.Debug().
Str("namespace", operatorOpts.Namespace).
Str("name", optsConfigMap).
Msg("getting options from configmap...")

ctx, canc := context.WithTimeout(context.Background(), 10*time.Second)
cfg, err := cluster.GetFilesFromConfigMap(ctx, operatorOpts.Namespace, optsConfigMap)
if err != nil {
canc()
return fmt.Errorf("cannot get configmap: %w", err)
}
canc()

fileOptions = cfg[0]
}

if len(fileOptions) > 0 {
if err := yaml.Unmarshal(fileOptions, &decodedFileOptions); err != nil {
return fmt.Errorf("cannot decode options %s: %w", optsPath, err)
}
}

if !cmd.Flag("endpoints").Changed {
opts.Endpoints = decodedFileOptions.Endpoints
}

if !cmd.Flag("prefix").Changed {
opts.Prefix = decodedFileOptions.Prefix
}
}

// -- Get the credentials secret
if credentialsSecret != "" {
l.Debug().
Str("namespace", operatorOpts.Namespace).
Str("name", credentialsSecret).
Msg("getting credentials from secret...")

ctx, canc := context.WithTimeout(context.Background(), 10*time.Second)
secret, err := cluster.GetDataFromSecret(ctx, operatorOpts.Namespace, credentialsSecret)
if err != nil {
canc()
return fmt.Errorf("cannot get secret: %w", err)
}
canc()

username, exists := secret["username"]
if !exists {
return fmt.Errorf("secret %s does not contain any username", credentialsSecret)
} else {
if opts.Username == "" {
// The user did not overwrite this via flag.
// So we're going to the value from the secret.
opts.Username = string(username)
}
}

if password, exists := secret["password"]; exists {
opts.Password = string(password)
}
}

// -- Ask for password
if tmpPassFlag {
if operatorOpts.RunningInK8s {
return fmt.Errorf("cannot use --password while running in Kubernetes. Please use a Secret instead.")
}

fmt.Printf("Please enter password for %s: ", opts.Username)
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return fmt.Errorf("cannot read password from terminal: %w", err)
}
fmt.Println()

opts.Password = strings.TrimSpace(string(bytePassword))
}

return nil
},
RunE: func(_ *cobra.Command, _ []string) error {
return runWithEtcd(operatorOpts, opts)
},
Example: "etcd --username root -p --endpoints 10.10.10.10:2379,10.10.10.11:2379",
}

// -----------------------------
// Flags
// -----------------------------

cmd.Flags().StringVarP(&opts.Username, "username", "u", "",
"username for authentication")
cmd.Flags().BoolVarP(&tmpPassFlag, "password", "p", false,
"enter password -- will be done interactively.")
cmd.Flags().StringVar(&opts.Prefix, "prefix", "",
"prefix to insert before every key.")
cmd.Flags().StringSliceVar(&opts.Endpoints, "endpoints", []string{"localhost:2379"},
"list of endpoints for etcd.")
cmd.Flags().StringVar(&optsPath, "options-path", "",
"path to the file containing service directory options.")
cmd.Flags().StringVar(&optsConfigMap, "options-configmap", func() string {
if operatorOpts.RunningInK8s {
return defaultEtcdConfigMapName
}

return ""
}(),
"name of the Kubernetes config map containing settings.")
cmd.Flags().StringVar(&credentialsSecret, "credentials-secret", func() string {
if operatorOpts.RunningInK8s {
return defaultEtcdCredentialsSecretName
}

return ""
}(),
"name of the Kubernetes secret containing the credentials.")

return cmd
}

func runWithEtcd(operatorOpts *Options, opts *EtcdOptions) error {
ctx, canc := context.WithTimeout(context.Background(), 15*time.Second)
defer canc()

spew.Dump(opts)

// TODO: support certificates
cli, err := clientv3.New(clientv3.Config{
Endpoints: opts.Endpoints,
Username: opts.Username,
Password: opts.Password,
DialTimeout: 15 * time.Second,
})

if err != nil {
return fmt.Errorf("cannot get etcd client: %w", err)
}

defer cli.Close()

servreg := etcd.NewServiceRegistryWithEtcd(ctx, cli, &opts.Prefix)

// TODO: use the handler
_ = servreg

return nil
}
2 changes: 1 addition & 1 deletion pkg/command/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ func GetRunCommand() *cobra.Command {
// Sub commands
// -----------------------------

// TODO: add commands
cmd.AddCommand(getRunEtcdCommand(opts))

return cmd
}
Expand Down

0 comments on commit 3e97414

Please sign in to comment.