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

Improve the code to print the secrets using printTable #464

Merged
merged 13 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
40 changes: 23 additions & 17 deletions pkg/cmd/get/clusters.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package get

import (
"bytes"
"context"
"fmt"
"github.com/cnoe-io/idpbuilder/api/v1alpha1"
Expand All @@ -10,13 +9,14 @@ import (
"github.com/cnoe-io/idpbuilder/pkg/kind"
"github.com/cnoe-io/idpbuilder/pkg/util"
"github.com/spf13/cobra"
"io"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
"os"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/kind/pkg/cluster"
"slices"
Expand Down Expand Up @@ -74,9 +74,27 @@ func list(cmd *cobra.Command, args []string) error {
if err != nil {
return err
} else {
// Convert the list of the clusters to Table of clusters
printTable(printers.PrintOptions{}, generateClusterTable(clusters))
return nil
// Convert the list of the clusters to a Table of clusters and print the table using the format selected
err := printClustersOutput(os.Stdout, clusters, outputFormat)
if err != nil {
return err
} else {
return nil
}
}
}

func printClustersOutput(outWriter io.Writer, clusters []Cluster, format string) error {
switch format {
case "json":
return util.PrintDataAsJson(clusters, outWriter)
case "yaml":
return util.PrintDataAsYaml(clusters, outWriter)
case "":
return util.PrintTable(generateClusterTable(clusters), outWriter)
default:

return fmt.Errorf("output format %s is not supported", format)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we combine this function for secret and cluster both and add it in utils package to avoid duplicate code?

Copy link
Contributor Author

@cmoulliard cmoulliard Dec 20, 2024

Choose a reason for hiding this comment

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

Can we combine this function for secret and cluster both and add it in utils package to avoid duplicate code?

Here is what I did which is still a WIP: 580042f, 13b4ad9

Improvements are welcome ;-) @punkwalker

}
}

Expand Down Expand Up @@ -232,18 +250,6 @@ func generateClusterTable(clusterTable []Cluster) metav1.Table {
return *table
}

func printTable(opts printers.PrintOptions, table metav1.Table) {
logger := helpers.CmdLogger
out := bytes.NewBuffer([]byte{})
printer := printers.NewTablePrinter(opts)
err := printer.PrintObj(&table, out)
if err != nil {
logger.Error(err, "failed to print the table.")
return
}
fmt.Println(out.String())
}

func generateNodeData(nodes []Node) string {
var result string
for i, aNode := range nodes {
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/get/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func init() {
GetCmd.AddCommand(ClustersCmd)
GetCmd.AddCommand(SecretsCmd)
GetCmd.PersistentFlags().StringSliceVarP(&packages, "packages", "p", []string{}, "names of packages.")
GetCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "", "Output format. json or yaml.")
GetCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "", "Output format: table (default if not specified), json or yaml.")
Copy link
Contributor

Choose a reason for hiding this comment

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

let's name the default as "table" instead of "".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree. Fixed: 288284e

GetCmd.PersistentFlags().StringVarP(&helpers.KubeConfigPath, "kubeconfig", "", "", "kube config file Path.")
}

Expand Down
150 changes: 91 additions & 59 deletions pkg/cmd/get/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package get

import (
"context"
"embed"
"encoding/json"
"fmt"
"github.com/cnoe-io/idpbuilder/pkg/util"
"io"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"os"
"path/filepath"
"text/template"
"strings"

"github.com/cnoe-io/idpbuilder/api/v1alpha1"
"github.com/cnoe-io/idpbuilder/pkg/build"
Expand All @@ -20,19 +20,14 @@ import (
"k8s.io/apimachinery/pkg/selection"
"k8s.io/client-go/util/homedir"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
)

const (
secretTemplatePath = "templates/secrets.tmpl"
argoCDAdminUsername = "admin"
argoCDInitialAdminSecretName = "argocd-initial-admin-secret"
giteaAdminSecretName = "gitea-credential"
)

//go:embed templates
var templates embed.FS

var SecretsCmd = &cobra.Command{
Use: "secrets",
Short: "retrieve secrets from the cluster",
Expand All @@ -42,17 +37,29 @@ var SecretsCmd = &cobra.Command{
}

// well known secrets that are part of the core packages
var corePkgSecrets = map[string][]string{
"argocd": []string{argoCDInitialAdminSecretName},
"gitea": []string{giteaAdminSecretName},
}
var (
corePkgSecrets = map[string][]string{
"argocd": []string{argoCDInitialAdminSecretName},
"gitea": []string{giteaAdminSecretName},
}
)

type TemplateData struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Data map[string]string `json:"data"`
}

type Secret struct {
isCore bool
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the use of isCore field just to control the printing of core package fields? Can we do that without this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is the use of isCore field just to control the printing of core package fields?

It is used to figure out when we have a core package secret containing as values: username, password and token (for gitea)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can we do that without this?

Not really

Name string `json:"name"`
Namespace string `json:"namespace"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Token string `json:"token,omitempty"`
Data map[string]string `json:"data,omitempty"`
}

func getSecretsE(cmd *cobra.Command, args []string) error {
ctx, ctxCancel := context.WithCancel(cmd.Context())
defer ctxCancel()
Expand Down Expand Up @@ -85,7 +92,7 @@ func getSecretsE(cmd *cobra.Command, args []string) error {

func printAllPackageSecrets(ctx context.Context, outWriter io.Writer, kubeClient client.Client, format string) error {
selector := labels.NewSelector()
secretsToPrint := make([]any, 0, 2)
secrets := []Secret{}

for k, v := range corePkgSecrets {
for i := range v {
Expand All @@ -96,29 +103,29 @@ func printAllPackageSecrets(ctx context.Context, outWriter io.Writer, kubeClient
}
return fmt.Errorf("getting secret %s in %s: %w", v[i], k, sErr)
}
secretsToPrint = append(secretsToPrint, secretToTemplateData(secret))
secrets = append(secrets, generateSecret(secret, true))
}
}

secrets, err := getSecretsByCNOELabel(ctx, kubeClient, selector)
cnoeLabelSecrets, err := getSecretsByCNOELabel(ctx, kubeClient, selector)
if err != nil {
return fmt.Errorf("listing secrets: %w", err)
}

for i := range secrets.Items {
secretsToPrint = append(secretsToPrint, secretToTemplateData(secrets.Items[i]))
for i := range cnoeLabelSecrets.Items {
secrets = append(secrets, generateSecret(cnoeLabelSecrets.Items[i], false))
}

if len(secretsToPrint) == 0 {
if len(secrets) == 0 {
fmt.Println("no secrets found")
return nil
}
return printOutput(secretTemplatePath, outWriter, secretsToPrint, format)
return printSecretsOutput(outWriter, secrets, format)
}

func printPackageSecrets(ctx context.Context, outWriter io.Writer, kubeClient client.Client, format string) error {
selector := labels.NewSelector()
secretsToPrint := make([]any, 0, 2)
secrets := []Secret{}

for i := range packages {
p := packages[i]
Expand All @@ -132,7 +139,7 @@ func printPackageSecrets(ctx context.Context, outWriter io.Writer, kubeClient cl
}
return fmt.Errorf("getting secret %s in %s: %w", secretNames[j], p, sErr)
}
secretsToPrint = append(secretsToPrint, secretToTemplateData(secret))
secrets = append(secrets, generateSecret(secret, true))
}
continue
}
Expand All @@ -144,69 +151,94 @@ func printPackageSecrets(ctx context.Context, outWriter io.Writer, kubeClient cl

pkgSelector := selector.Add(*req)

secrets, pErr := getSecretsByCNOELabel(ctx, kubeClient, pkgSelector)
if pErr != nil {
return fmt.Errorf("listing secrets: %w", pErr)
cnoeLabelSecrets, err := getSecretsByCNOELabel(ctx, kubeClient, pkgSelector)
if err != nil {
return fmt.Errorf("listing secrets: %w", err)
}

for i := range cnoeLabelSecrets.Items {
secrets = append(secrets, generateSecret(cnoeLabelSecrets.Items[i], false))
}

for j := range secrets.Items {
secretsToPrint = append(secretsToPrint, secretToTemplateData(secrets.Items[j]))
if len(secrets) == 0 {
fmt.Println("no secrets found")
return nil
}
}

return printOutput(secretTemplatePath, outWriter, secretsToPrint, format)
return printSecretsOutput(outWriter, secrets, format)
}

func renderTemplate(templatePath string, outWriter io.Writer, data []any) error {
tmpl, err := templates.ReadFile(templatePath)
if err != nil {
return fmt.Errorf("failed to read template: %w", err)
func generateSecretTable(secretTable []Secret) metav1.Table {
table := &metav1.Table{}
table.ColumnDefinitions = []metav1.TableColumnDefinition{
{Name: "Name", Type: "string"},
{Name: "Namespace", Type: "string"},
{Name: "Username", Type: "string"},
{Name: "Password", Type: "string"},
{Name: "Token", Type: "string"},
{Name: "Data", Type: "string"},
}
for _, secret := range secretTable {
var dataEntries []string

t, err := template.New("secrets").Parse(string(tmpl))
if err != nil {
return fmt.Errorf("parsing template: %w", err)
}
for i := range data {
tErr := t.Execute(outWriter, data[i])
if tErr != nil {
return fmt.Errorf("executing template for data %s : %w", data[i], tErr)
if !secret.isCore {
for key, value := range secret.Data {
dataEntries = append(dataEntries, fmt.Sprintf("%s=%s", key, value))
}
}
dataString := strings.Join(dataEntries, ", ")
row := metav1.TableRow{
Cells: []interface{}{
secret.Name,
secret.Namespace,
secret.Username,
secret.Password,
secret.Token,
dataString,
},
}
table.Rows = append(table.Rows, row)
}
return nil
return *table
}

func printOutput(templatePath string, outWriter io.Writer, data []any, format string) error {
func printSecretsOutput(outWriter io.Writer, secrets []Secret, format string) error {
switch format {
case "json":
enc := json.NewEncoder(outWriter)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
return enc.Encode(data)
return util.PrintDataAsJson(secrets, outWriter)
case "yaml":
b, err := yaml.Marshal(data)
if err != nil {
return err
}
_, err = outWriter.Write(b)
return err
case "":
return renderTemplate(templatePath, outWriter, data)
return util.PrintDataAsYaml(secrets, outWriter)
case "", "table":
return util.PrintTable(generateSecretTable(secrets), outWriter)
default:
return fmt.Errorf("output format %s is not supported", format)
}
}

func secretToTemplateData(s v1.Secret) TemplateData {
data := TemplateData{
func generateSecret(s v1.Secret, isCoreSecret bool) Secret {
secret := Secret{
Name: s.Name,
Namespace: s.Namespace,
Copy link
Contributor

Choose a reason for hiding this comment

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

We should name this populateSecrets instead of generateSecret. I felt we are creating a new secret.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed: 813c0cd

Data: make(map[string]string),
}
for k, v := range s.Data {
data.Data[k] = string(v)

if isCoreSecret {
secret.isCore = true
secret.Username = string(s.Data["username"])
secret.Password = string(s.Data["password"])
secret.Token = string(s.Data["token"])
secret.Data = nil
} else {
newData := make(map[string]string)
for key, value := range s.Data {
newData[key] = string(value)
}
if len(newData) > 0 {
secret.Data = newData
}
}
return data

return secret
}

func getSecretsByCNOELabel(ctx context.Context, kubeClient client.Client, l labels.Selector) (v1.SecretList, error) {
Expand Down
Loading
Loading