Skip to content

Commit

Permalink
implement multi-cluster
Browse files Browse the repository at this point in the history
Signed-off-by: Chanwit Kaewkasi <chanwit@gmail.com>
  • Loading branch information
chanwit committed Feb 25, 2024
1 parent 1025dce commit 0ceeffe
Show file tree
Hide file tree
Showing 9 changed files with 387 additions and 17 deletions.
139 changes: 139 additions & 0 deletions cmd/flamingo/add_cluster.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package main

import (
ctx "context"
"encoding/base64"
"fmt"
"os"

"github.com/flux-subsystem-argo/flamingo/pkg/utils"
"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
)

var addClusterCmd = &cobra.Command{
Use: "add-cluster CONTEXT_NAME",
Short: "Add a cluster to Flamingo",
Long: `
# Add a cluster to Flamingo
flamingo add-cluster my-cluster
# Add cluster dev-1, override the name and address, and skip TLS verification
flamingo add-cluster dev-1 \
--server-name=dev-1.example.com \
--server-addr=https://dev-1.example.com:6443 \
--insecure
`,
Args: cobra.ExactArgs(1),
RunE: addClusterCmdRun,
}

var addClusterFlags struct {
insecureSkipTLSVerify bool
serverName string
serverAddress string
export bool
}

func init() {
addClusterCmd.Flags().BoolVar(&addClusterFlags.insecureSkipTLSVerify, "insecure", false, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure")
addClusterCmd.Flags().StringVar(&addClusterFlags.serverName, "server-name", "", "If set, this overrides the hostname used to validate the server certificate")
addClusterCmd.Flags().StringVar(&addClusterFlags.serverAddress, "server-addr", "", "If set, this overrides the server address used to connect to the cluster")
addClusterCmd.Flags().BoolVar(&addClusterFlags.export, "export", false, "export manifests instead of installing")

rootCmd.AddCommand(addClusterCmd)
}

func addClusterCmdRun(cmd *cobra.Command, args []string) error {
leafClusterContext := args[0]

kubeconfig := ""
if *kubeconfigArgs.KubeConfig == "" {
kubeconfig = clientcmd.RecommendedHomeFile
} else {
kubeconfig = *kubeconfigArgs.KubeConfig
}

// Load the kubeconfig file
config, err := clientcmd.LoadFromFile(kubeconfig)
if err != nil {
return err
}

// Specify the context name
contextName := leafClusterContext

// Get the context
context, exists := config.Contexts[contextName]
if !exists {
return fmt.Errorf("context not found")
}

// Get the cluster info
cluster, exists := config.Clusters[context.Cluster]
if !exists {
return fmt.Errorf("cluster not found")
}

// Get the user info
user, exists := config.AuthInfos[context.AuthInfo]
if !exists {
return fmt.Errorf("user not found")
}

template := `apiVersion: v1
kind: Secret
metadata:
name: %s-cluster
namespace: argocd
labels:
argocd.argoproj.io/secret-type: cluster
flamingo/cluster: "true"
annotations:
flamingo/external-address: "%s"
flamingo/internal-address: "%s"
type: Opaque
stringData:
name: %s
server: %s
config: |
{
"tlsClientConfig": {
"insecure": %v,
"certData": "%s",
"keyData": "%s",
"serverName": "%s"
}
}
`
serverAddress := addClusterFlags.serverAddress
if serverAddress == "" {
serverAddress = cluster.Server
}

result := fmt.Sprintf(template,
contextName,
cluster.Server, // external address (known to the user via kubectl config view)
serverAddress, // internal address
contextName,
serverAddress,
addClusterFlags.insecureSkipTLSVerify,
base64.StdEncoding.EncodeToString(user.ClientCertificateData),
base64.StdEncoding.EncodeToString(user.ClientKeyData),
addClusterFlags.serverName,
)

if addClusterFlags.export {
fmt.Print(result)
return nil
} else {
logger.Actionf("applying generated cluster secret %s in %s namespace", contextName+"-cluster", rootArgs.applicationNamespace)
applyOutput, err := utils.Apply(ctx.Background(), kubeconfigArgs, kubeclientOptions, []byte(result))
if err != nil {
return fmt.Errorf("apply failed: %w", err)
}
fmt.Fprintln(os.Stderr, applyOutput)
}

return nil
}
50 changes: 41 additions & 9 deletions cmd/flamingo/generate_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var generateAppCmd = &cobra.Command{
Use: "generate-app NAME",
Aliases: []string{"gen-app"},
Args: cobra.ExactArgs(1),
Short: "Generate a Flamingo application from Flux resources",
Short: "Generate a Flamingo application from Flux resources (Kustomization or HelmRelease)",
Long: `
# Generate a Flamingo application from a Flux Kustomization podinfo in the current namespace (flux-system).
# The generated application is put in the argocd namespace by default.
Expand All @@ -31,6 +31,12 @@ flamingo generate-app hr/podinfo
# Generate a Flamingo application from a HelmRelease podinfo in the podinfo namespace.
flamingo generate-app -n podinfo hr/podinfo
# Generate a Flamingo application named podinfo-ks, from a Flux Kustomization podinfo in the podinfo-kustomize namespace of the dev-1 cluster.
# The generated application is put in the argocd namespace of the current cluster.
flamingo generate-app \
--app-name=podinfo-ks \
-n podinfo-kustomize dev-1/ks/podinfo
`,
RunE: generateAppCmdRun,
}
Expand All @@ -48,16 +54,24 @@ func init() {
}

func generateAppCmdRun(_ *cobra.Command, args []string) error {
cli, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return err
}

kindSlashName := args[0]
isValid := false
clusterName := ""
kindName := ""
objectName := ""

kindSlashName := ""
// FQN: fully qualified name is in the format of kind/name cluster-name/kind/name
fqn := args[0]
if strings.Count(fqn, "/") == 1 {
clusterName = "in-cluster"
kindSlashName = fqn
} else if strings.Count(fqn, "/") == 2 {
clusterName = strings.SplitN(fqn, "/", 2)[0]
kindSlashName = strings.SplitN(fqn, "/", 2)[1]
} else {
return fmt.Errorf("not a valid Kustomization or HelmRelease resource")
}

// Define a map for valid kinds with their short and full names
var validKinds = map[string]string{
"ks": kustomizev1.KustomizationKind,
Expand Down Expand Up @@ -90,13 +104,31 @@ func generateAppCmdRun(_ *cobra.Command, args []string) error {
appName = objectName
}

mgmtCli, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return err
}

leafCli := mgmtCli
var clusterConfig *utils.ClusterConfig
if clusterName != "in-cluster" && clusterName != "" {
leafCli, clusterConfig, err = utils.KubeClientForLeafCluster(mgmtCli, clusterName, kubeclientOptions)
if err != nil {
return err
}
} else {
clusterConfig = &utils.ClusterConfig{
Server: "https://kubernetes.default.svc",
}
}

var tpl bytes.Buffer
if kindName == kustomizev1.KustomizationKind {
if err := generateKustomizationApp(cli, appName, objectName, kindName, &tpl); err != nil {
if err := generateKustomizationApp(leafCli, appName, objectName, kindName, clusterName, clusterConfig.Server, &tpl); err != nil {
return err
}
} else if kindName == helmv2b1.HelmReleaseKind {
if err := generateHelmReleaseApp(cli, appName, objectName, kindName, &tpl); err != nil {
if err := generateHelmReleaseApp(leafCli, appName, objectName, kindName, clusterName, clusterConfig.Server, &tpl); err != nil {
return err
}
}
Expand Down
11 changes: 9 additions & 2 deletions cmd/flamingo/generate_app_hr.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ metadata:
flamingo/workload-type: "{{ .WorkloadType }}"
flamingo/source-type: "{{ .SourceType }}"
flamingo/destination-namespace: "{{ .DestinationNamespace }}"
flamingo/cluster-name: "{{ .ClusterName }}"
annotations:
weave.gitops.flamingo/base-url: "http://localhost:9001"
weave.gitops.flamingo/cluster-name: "Default"
spec:
destination:
namespace: {{ .DestinationNamespace }}
server: https://kubernetes.default.svc
server: {{ .Server }}
project: default
source:
chart: {{ .ChartName }}
Expand All @@ -44,7 +45,7 @@ spec:
- FluxSubsystem=true
`

func generateHelmReleaseApp(c client.Client, appName, objectName string, kindName string, tpl *bytes.Buffer) error {
func generateHelmReleaseApp(c client.Client, appName string, objectName string, kindName string, clusterName string, server string, tpl *bytes.Buffer) error {
object := helmv2b1.HelmRelease{
ObjectMeta: metav1.ObjectMeta{
Name: objectName,
Expand Down Expand Up @@ -75,6 +76,9 @@ func generateHelmReleaseApp(c client.Client, appName, objectName string, kindNam
WorkloadType string
SourceType string
DestinationNamespace string
ClusterName string

Server string

ChartName string
ChartURL string
Expand All @@ -89,6 +93,9 @@ func generateHelmReleaseApp(c client.Client, appName, objectName string, kindNam
params.WorkloadName = object.Name
params.WorkloadType = kindName
params.SourceType = sourceKind
params.ClusterName = clusterName

params.Server = server

params.ChartName = object.Spec.Chart.Spec.Chart
params.ChartRevision = object.Spec.Chart.Spec.Version
Expand Down
11 changes: 9 additions & 2 deletions cmd/flamingo/generate_app_ks.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ metadata:
flamingo/workload-type: "{{ .WorkloadType }}"
flamingo/source-type: "{{ .SourceType }}"
flamingo/destination-namespace: "{{ .DestinationNamespace }}"
flamingo/cluster-name: "{{ .ClusterName }}"
annotations:
weave.gitops.flamingo/base-url: "http://localhost:9001"
weave.gitops.flamingo/cluster-name: "Default"
spec:
destination:
namespace: {{ .DestinationNamespace }}
server: https://kubernetes.default.svc
server: {{ .Server }}
project: default
source:
path: {{ .Path }}
Expand All @@ -44,7 +45,7 @@ spec:
- FluxSubsystem=true
`

func generateKustomizationApp(c client.Client, appName, objectName string, kindName string, tpl *bytes.Buffer) error {
func generateKustomizationApp(c client.Client, appName string, objectName string, kindName string, clusterName string, server string, tpl *bytes.Buffer) error {
object := kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: objectName,
Expand Down Expand Up @@ -75,6 +76,9 @@ func generateKustomizationApp(c client.Client, appName, objectName string, kindN
WorkloadName string
SourceType string
DestinationNamespace string
ClusterName string

Server string

Path string
SourceURL string
Expand All @@ -88,6 +92,9 @@ func generateKustomizationApp(c client.Client, appName, objectName string, kindN
params.WorkloadName = object.Name
params.WorkloadType = kindName
params.SourceType = sourceKind
params.ClusterName = clusterName

params.Server = server

// The default path is '.' unless provided by the object
params.Path = "."
Expand Down
6 changes: 4 additions & 2 deletions cmd/flamingo/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,15 @@ func getCmdRun(cmd *cobra.Command, args []string) error {
}

w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "NAMESPACE\tAPP-NS\tAPP\tFLUX-TYPE\tSOURCE-TYPE\tSTATUS\tMESSAGE")
fmt.Fprintln(w, "NAMESPACE\tAPP-NS\tAPP\tFLUX-TYPE\tSOURCE-TYPE\tCLUSTER\tSTATUS\tMESSAGE")
for _, item := range list.Items {
labels := item.GetLabels()
// Extract the necessary fields from the Unstructured object
// This is just an example, you'll need to adjust based on the actual structure of your Argo CD objects
appType := labels["flamingo/workload-type"]
sourceType := labels["flamingo/source-type"]
objectNs := labels["flamingo/destination-namespace"]
clusterName := labels["flamingo/cluster-name"]
status, err := extractStatus(&item)
if err != nil {
status = err.Error()
Expand All @@ -82,12 +83,13 @@ func getCmdRun(cmd *cobra.Command, args []string) error {
if len(message) > 40 {
message = message[:40] + " ..."
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
objectNs,
item.GetNamespace(),
item.GetName(),
appType,
sourceType,
clusterName,
status,
message)
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/flamingo/list_candidates.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ const url = "https://raw.githubusercontent.com/flux-subsystem-argo/flamingo/main

var listCandidates = &cobra.Command{
Use: "list-candidates",
Short: "List candidates",
Aliases: []string{"lc"},
Aliases: []string{"list-candidate", "candidates", "candidate"},
Short: "List installation candidates",
RunE: listCmdRun,
}

Expand Down
Loading

0 comments on commit 0ceeffe

Please sign in to comment.