1 package utils
2
3 import (
4 "context"
5 "encoding/base64"
6 "flag"
7 "fmt"
8
9 containerAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/container/v1beta1"
10 "github.com/peterbourgon/ff/v3"
11 "github.com/peterbourgon/ff/v3/ffcli"
12 "google.golang.org/api/container/v1"
13 "k8s.io/apimachinery/pkg/runtime"
14 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
15 "k8s.io/client-go/tools/clientcmd"
16 "k8s.io/client-go/tools/clientcmd/api"
17 "sigs.k8s.io/controller-runtime/pkg/client"
18
19 "edge-infra.dev/pkg/lib/cli/commands"
20 )
21
22 const (
23 LongHelp = `
24 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.
25
26 The cli program can be run like so:
27
28 lumberjack --bannerInfraCluster=someBannerInfraCluster --topLevelProjectID=someForemanProject
29
30 The --bannerInfraCluster and --topLevelProjectID flags are required.`
31 )
32
33 type LumberjackCommand struct {
34 Client client.Client
35 }
36
37 func New() *LumberjackCommand {
38 return &LumberjackCommand{}
39 }
40
41 func (l *LumberjackCommand) SetClusterConnection(client *client.Client) *LumberjackCommand {
42 l.Client = *client
43 return l
44 }
45
46 func (l *LumberjackCommand) Exec(name, shortUsage, shortHelp string, fs *flag.FlagSet) *ffcli.Command {
47 return &ffcli.Command{
48 Name: name,
49 ShortUsage: shortUsage,
50 ShortHelp: shortHelp,
51 LongHelp: LongHelp,
52 FlagSet: fs,
53 Options: []ff.Option{ff.WithEnvVarNoPrefix()},
54 Subcommands: []*ffcli.Command{
55 commands.Version(),
56 },
57 Exec: func(ctx context.Context, _ []string) error {
58 return l.addLoggingConfig(ctx)
59 },
60 }
61 }
62
63 func GetClient(ctx context.Context, projectID, clusterName, zone string) (*client.Client, error) {
64 kubeConfig, name, err := getClusterConfig(ctx, projectID, clusterName, zone)
65 if err != nil {
66 return nil, err
67 }
68 cfg, err := clientcmd.NewNonInteractiveClientConfig(*kubeConfig, name, &clientcmd.ConfigOverrides{CurrentContext: name}, nil).ClientConfig()
69 if err != nil {
70 fmt.Println(err)
71 return nil, fmt.Errorf("failed to create kube config for cluster=%s: %w", clusterName, err)
72 }
73 client, err := client.New(cfg, client.Options{Scheme: createScheme()})
74 if err != nil {
75 return nil, fmt.Errorf("failed to connect to cluster=%s: %w", clusterName, err)
76 }
77 return &client, nil
78 }
79
80 func getClusterConfig(ctx context.Context, projectID, clusterName, zone string) (*api.Config, string, error) {
81 svc, err := container.NewService(ctx)
82 if err != nil {
83 return nil, "", fmt.Errorf("container.NewService: %w", err)
84 }
85
86 containerCluster, err := svc.Projects.Zones.Clusters.Get(projectID, zone, clusterName).Context(ctx).Do()
87 if err != nil {
88 fmt.Println(err)
89 return nil, "", fmt.Errorf("failed to get container cluster information: %w", err)
90 }
91 name := fmt.Sprintf("gke_%s_%s_%s", projectID, containerCluster.Zone, containerCluster.Name)
92 cert, err := base64.StdEncoding.DecodeString(containerCluster.MasterAuth.ClusterCaCertificate)
93 if err != nil {
94 return nil, "", fmt.Errorf("invalid certificate cluster=%s cert=%s: %w", name, containerCluster.MasterAuth.ClusterCaCertificate, err)
95 }
96
97 ret := api.Config{
98 Clusters: map[string]*api.Cluster{
99 name: {
100 CertificateAuthorityData: cert,
101 Server: "https://" + containerCluster.Endpoint,
102 },
103 },
104 Contexts: map[string]*api.Context{
105 name: {
106 Cluster: name,
107 AuthInfo: name,
108 },
109 },
110 AuthInfos: map[string]*api.AuthInfo{
111 name: {
112 AuthProvider: &api.AuthProviderConfig{
113 Name: "gcp",
114 Config: map[string]string{
115 "scopes": "https://www.googleapis.com/auth/cloud-platform",
116 },
117 },
118 },
119 },
120 }
121 return &ret, name, nil
122 }
123
124 func (l *LumberjackCommand) addLoggingConfig(ctx context.Context) error {
125 containerClusterList := &containerAPI.ContainerClusterList{}
126
127 if err := l.Client.List(ctx, containerClusterList, &client.ListOptions{}); err != nil {
128 return fmt.Errorf("failed to list namespaces for cluster, %w", err)
129 }
130 for _, cc := range containerClusterList.Items {
131 if cc.Spec.LoggingConfig != nil && len(cc.Spec.LoggingConfig.EnableComponents) == 1 && cc.Spec.LoggingConfig.EnableComponents[0] == "SYSTEM_COMPONENTS" {
132 continue
133 }
134 fmt.Println("adding logging config to", cc.Name)
135 if err := l.patchLoggingConfig(ctx, cc); err != nil {
136 return err
137 }
138 }
139 return nil
140 }
141
142 func (l *LumberjackCommand) patchLoggingConfig(ctx context.Context, cc containerAPI.ContainerCluster) error {
143 ccPatch := client.MergeFrom(cc.DeepCopy())
144 cc.Spec.LoggingConfig = &containerAPI.ClusterLoggingConfig{
145 EnableComponents: []string{"SYSTEM_COMPONENTS"},
146 }
147 return l.Client.Patch(ctx, &cc, ccPatch)
148 }
149
150 func createScheme() *runtime.Scheme {
151 scheme := runtime.NewScheme()
152 utilruntime.Must(containerAPI.AddToScheme(scheme))
153 return scheme
154 }
155
View as plain text