package utils import ( "context" "encoding/base64" "flag" "fmt" containerAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/container/v1beta1" "github.com/peterbourgon/ff/v3" "github.com/peterbourgon/ff/v3/ffcli" "google.golang.org/api/container/v1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" "sigs.k8s.io/controller-runtime/pkg/client" "edge-infra.dev/pkg/lib/cli/commands" ) const ( LongHelp = ` CLI program written to append container cluster resources deployed on a banner infra cluster with a logging config. This config will tell fluentbit-gke to only forward system logs. The cli program can be run like so: lumberjack --bannerInfraCluster=someBannerInfraCluster --topLevelProjectID=someForemanProject The --bannerInfraCluster and --topLevelProjectID flags are required.` ) type LumberjackCommand struct { Client client.Client } func New() *LumberjackCommand { return &LumberjackCommand{} } func (l *LumberjackCommand) SetClusterConnection(client *client.Client) *LumberjackCommand { l.Client = *client return l } func (l *LumberjackCommand) Exec(name, shortUsage, shortHelp string, fs *flag.FlagSet) *ffcli.Command { return &ffcli.Command{ Name: name, ShortUsage: shortUsage, ShortHelp: shortHelp, LongHelp: LongHelp, FlagSet: fs, Options: []ff.Option{ff.WithEnvVarNoPrefix()}, Subcommands: []*ffcli.Command{ commands.Version(), }, Exec: func(ctx context.Context, _ []string) error { return l.addLoggingConfig(ctx) }, } } func GetClient(ctx context.Context, projectID, clusterName, zone string) (*client.Client, error) { kubeConfig, name, err := getClusterConfig(ctx, projectID, clusterName, zone) if err != nil { return nil, err } cfg, err := clientcmd.NewNonInteractiveClientConfig(*kubeConfig, name, &clientcmd.ConfigOverrides{CurrentContext: name}, nil).ClientConfig() if err != nil { fmt.Println(err) return nil, fmt.Errorf("failed to create kube config for cluster=%s: %w", clusterName, err) } client, err := client.New(cfg, client.Options{Scheme: createScheme()}) if err != nil { return nil, fmt.Errorf("failed to connect to cluster=%s: %w", clusterName, err) } return &client, nil } func getClusterConfig(ctx context.Context, projectID, clusterName, zone string) (*api.Config, string, error) { svc, err := container.NewService(ctx) if err != nil { return nil, "", fmt.Errorf("container.NewService: %w", err) } containerCluster, err := svc.Projects.Zones.Clusters.Get(projectID, zone, clusterName).Context(ctx).Do() if err != nil { fmt.Println(err) return nil, "", fmt.Errorf("failed to get container cluster information: %w", err) } name := fmt.Sprintf("gke_%s_%s_%s", projectID, containerCluster.Zone, containerCluster.Name) cert, err := base64.StdEncoding.DecodeString(containerCluster.MasterAuth.ClusterCaCertificate) if err != nil { return nil, "", fmt.Errorf("invalid certificate cluster=%s cert=%s: %w", name, containerCluster.MasterAuth.ClusterCaCertificate, err) } ret := api.Config{ Clusters: map[string]*api.Cluster{ name: { CertificateAuthorityData: cert, Server: "https://" + containerCluster.Endpoint, }, }, Contexts: map[string]*api.Context{ name: { Cluster: name, AuthInfo: name, }, }, AuthInfos: map[string]*api.AuthInfo{ name: { AuthProvider: &api.AuthProviderConfig{ Name: "gcp", Config: map[string]string{ "scopes": "https://www.googleapis.com/auth/cloud-platform", }, }, }, }, } return &ret, name, nil } func (l *LumberjackCommand) addLoggingConfig(ctx context.Context) error { containerClusterList := &containerAPI.ContainerClusterList{} if err := l.Client.List(ctx, containerClusterList, &client.ListOptions{}); err != nil { return fmt.Errorf("failed to list namespaces for cluster, %w", err) } for _, cc := range containerClusterList.Items { if cc.Spec.LoggingConfig != nil && len(cc.Spec.LoggingConfig.EnableComponents) == 1 && cc.Spec.LoggingConfig.EnableComponents[0] == "SYSTEM_COMPONENTS" { continue } fmt.Println("adding logging config to", cc.Name) if err := l.patchLoggingConfig(ctx, cc); err != nil { return err } } return nil } func (l *LumberjackCommand) patchLoggingConfig(ctx context.Context, cc containerAPI.ContainerCluster) error { ccPatch := client.MergeFrom(cc.DeepCopy()) cc.Spec.LoggingConfig = &containerAPI.ClusterLoggingConfig{ EnableComponents: []string{"SYSTEM_COMPONENTS"}, } return l.Client.Patch(ctx, &cc, ccPatch) } func createScheme() *runtime.Scheme { scheme := runtime.NewScheme() utilruntime.Must(containerAPI.AddToScheme(scheme)) return scheme }