Skip to content

Commit

Permalink
Create a kompanion command to migrate KCC.
Browse files Browse the repository at this point in the history
New mgirator command.
Creates a migration file of KCC resources.
Allows the resources to be easily copied to another cluster.
Factored in changes suggested by Justin.
  • Loading branch information
cheftako committed Nov 27, 2024
1 parent 706bf9a commit 12fa3cc
Show file tree
Hide file tree
Showing 11 changed files with 671 additions and 151 deletions.
32 changes: 32 additions & 0 deletions experiments/kompanion/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Copyright 2024 Google LLC
#
# 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.

## ---
## Basic build
## ---
.PHONY: build
build: bin
GOWORK=off go build -o bin/kompanion

bin:
mkdir bin

## ---
## Clean build and related artifacts to make the repo 'clean'
## ---
.PHONY: clean
clean:
rm -rf bin
rm -f migration.yaml

5 changes: 3 additions & 2 deletions experiments/kompanion/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ Experimental KCC companion tool to help troubleshoot, analyze and gather data ab

```
# Assumes pwd is <REPO_ROOT>/experiments/kompanion
$ GOWORK=off go build -o kompanion
$ mkdir bin
$ GOWORK=off go build -o bin/kompanion
```

## Export
Expand All @@ -33,4 +34,4 @@ The command will generate a timestamped report `tar.gz` file to use as a snapsho

# Light Roadmap

* [ ] Debug/ audit logs for the tool itself
* [ ] Debug/ audit logs for the tool itself
85 changes: 12 additions & 73 deletions experiments/kompanion/cmd/export/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/klog/v2"
"sigs.k8s.io/yaml"
)
Expand All @@ -59,59 +57,23 @@ const (
)

func BuildExportCmd() *cobra.Command {
var opts ExportOptions

opts := NewExportOptions()
cmd := &cobra.Command{
Use: "export",
Short: "export Config Connector resources",
Example: examples,
RunE: func(cmd *cobra.Command, args []string) error {
return RunExport(cmd.Context(), &opts)
return RunExport(cmd.Context(), opts)
},
Args: cobra.ExactArgs(0),
}

cmd.Flags().StringVarP(&opts.kubeconfig, kubeconfigFlag, "", opts.kubeconfig, "path to the kubeconfig file.")
cmd.Flags().StringVarP(&opts.reportNamePrefix, reportNamePrefixFlag, "", "report", "Prefix for the report name. The tool appends a timestamp to this in the format \"YYYYMMDD-HHMMSS.milliseconds\".")

cmd.Flags().StringArrayVarP(&opts.targetNamespaces, targetNamespacesFlag, "", []string{}, "namespace prefix to target the export tool. Targets all if empty. Can be specified multiple times.")
cmd.Flags().StringArrayVarP(&opts.ignoreNamespaces, ignoreNamespacesFlag, "", []string{"kube"}, "namespace prefix to ignore. Excludes nothing if empty. Can be specified multiple times. Defaults to \"kube\".")

cmd.Flags().StringArrayVarP(&opts.targetObjects, targetObjectsFlag, "", []string{}, "object name prefix to target. Targets all if empty. Can be specified multiple times.")
cmd.Flags().StringArrayVarP(&opts.ignoreObjects, ignoreObjectsFlag, "", []string{}, "object name prefix to ignore. Excludes nothing if empty. Can be specified multiple times.")

cmd.Flags().IntVarP(&opts.workerRountines, workerRoutinesFlag, "", 10, "Configure the number of worker routines to export namespaces with. Defaults to 10. ")
flags := cmd.Flags()
opts.AddFlags(flags)

return cmd
}

const (
// flag names.
kubeconfigFlag = "kubeconfig"
reportNamePrefixFlag = "report-prefix"

targetNamespacesFlag = "target-namespaces"
ignoreNamespacesFlag = "exclude-namespaces"

targetObjectsFlag = "target-objects"
ignoreObjectsFlag = "exclude-objects"

workerRoutinesFlag = "worker-routines"
)

type ExportOptions struct {
kubeconfig string
reportNamePrefix string

targetNamespaces []string
ignoreNamespaces []string

targetObjects []string
ignoreObjects []string

workerRountines int
}

// Task is implemented by our namespace-collection routine, or anything else we want to run in parallel.
type Task interface {
Run(ctx context.Context) error
Expand Down Expand Up @@ -217,37 +179,14 @@ func (t *dumpResourcesTask) Run(ctx context.Context) error {
return nil
}

func (opts *ExportOptions) validateFlags() error {
if opts.workerRountines <= 0 || opts.workerRountines > 100 {
return fmt.Errorf("invalid value %d for flag %s. Supported values are [1,100]", opts.workerRountines, workerRoutinesFlag)
}

return nil
}

func getRESTConfig(ctx context.Context, opts *ExportOptions) (*rest.Config, error) {
var loadingRules clientcmd.ClientConfigLoader
if opts.kubeconfig != "" {
loadingRules = &clientcmd.ClientConfigLoadingRules{ExplicitPath: opts.kubeconfig}
} else {
loadingRules = clientcmd.NewDefaultClientConfigLoadingRules()
}

return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
loadingRules,
&clientcmd.ConfigOverrides{
// ClusterInfo: clientcmdapi.Cluster{Server: masterUrl},
}).ClientConfig()
}

func RunExport(ctx context.Context, opts *ExportOptions) error {
log.Printf("Running kompanion export with kubeconfig: %s", opts.kubeconfig)
log.Printf("Running kompanion export with kubeconfig: %s", opts.Kubeconfig)

if err := opts.validateFlags(); err != nil {
return err
}

config, err := getRESTConfig(ctx, opts)
config, err := utils.GetRESTConfig(ctx, opts.Kubeconfig)
if err != nil {
return fmt.Errorf("error building kubeconfig: %w", err)
}
Expand Down Expand Up @@ -284,29 +223,29 @@ func RunExport(ctx context.Context, opts *ExportOptions) error {
return fmt.Errorf("error fetching namespaces: %w", err)
}

reportName := timestampedName(opts.reportNamePrefix)
reportName := timestampedName(opts.ReportNamePrefix)
reportTempDir := filepath.Join(".", reportName)
reportFile := filepath.Join(".", reportName+".tar.gz")
if err := os.Mkdir(filepath.Join(reportTempDir), 0o700); err != nil {
return fmt.Errorf("could not create %q directory: %w", reportTempDir, err)
}

shouldExcludeObject := func(id types.NamespacedName) bool {
if shouldExclude(id.Namespace, opts.ignoreNamespaces, opts.targetNamespaces) {
if shouldExclude(id.Namespace, opts.IgnoreNamespaces, opts.TargetNamespaces) {
return true
}
return shouldExclude(id.Name, opts.ignoreObjects, opts.targetObjects)
return shouldExclude(id.Name, opts.IgnoreObjects, opts.TargetObjects)
}

// create the work log for go routine workers to use
q := &taskQueue{}

// Parallize across resources, unless we are scoped to a few namespaces
// The thought is that if users target a particular namespace (or a few), they may not have cluster-wide permission.
perNamespace := len(opts.targetNamespaces) > 0
perNamespace := len(opts.TargetNamespaces) > 0
if perNamespace {
for _, ns := range namespaces.Items {
if shouldExclude(ns.Name, opts.ignoreNamespaces, opts.targetNamespaces) {
if shouldExclude(ns.Name, opts.IgnoreNamespaces, opts.TargetNamespaces) {
continue
}

Expand Down Expand Up @@ -335,7 +274,7 @@ func RunExport(ctx context.Context, opts *ExportOptions) error {
var errs []error
var errsMutex sync.Mutex

for i := 0; i < opts.workerRountines; i++ {
for i := 0; i < opts.WorkerRoutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
Expand Down
59 changes: 59 additions & 0 deletions experiments/kompanion/cmd/export/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2024 Google LLC
//
// 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 export

import (
"log"

"github.com/GoogleCloudPlatform/k8s-config-connector/experiments/kompanion/pkg/utils"
"github.com/spf13/pflag"
)

const (
reportNamePrefixFlag = "report-prefix"
)

type ExportOptions struct {
utils.ClusterCrawlOptions

ReportNamePrefix string
}

func (opts *ExportOptions) AddFlags(flags *pflag.FlagSet) {
opts.ClusterCrawlAddFlags(flags)

flags.StringVarP(&opts.ReportNamePrefix, reportNamePrefixFlag, "", opts.ReportNamePrefix, "Prefix for the report name. The tool appends a timestamp to this in the format \"YYYYMMDD-HHMMSS.milliseconds\".")
}

func (opts *ExportOptions) validateFlags() error {
if err := opts.ValidateClusterCrawlFlags(); err != nil {
return err
}

return nil
}

func (opts *ExportOptions) Print() {
opts.ClusterCrawlPrint()
log.Printf("reportNamePrefix set to %q.\n", opts.ReportNamePrefix)
}

func NewExportOptions() *ExportOptions {
opts := ExportOptions{
ReportNamePrefix: "report",
}
opts.ClusterCrawlOptions = utils.NewClusterCrawlOptions()
return &opts
}
Loading

0 comments on commit 12fa3cc

Please sign in to comment.