this repo has no description
1package cmd
2
3import (
4 "context"
5 "fmt"
6 "os/exec"
7 "path/filepath"
8 "strings"
9 "time"
10
11 "github.com/charmbracelet/log"
12 "github.com/spf13/cobra"
13
14 "github.com/khuedoan/cloudlab/toolbox/internal/cluster"
15)
16
17const (
18 registryNamespace = "registry"
19 registryService = "svc/registry"
20 registryPort = 5000
21 gitopsRepository = "platform"
22 gitopsTag = "latest"
23 fluxNamespace = "flux-system"
24 fluxSource = "platform"
25 fluxKustomization = "platform"
26)
27
28var (
29 gitopsPath string
30)
31
32func init() {
33 gitopsCmd.Flags().StringVar(&gitopsPath, "path", "", "Path to the manifest bundle to publish")
34 _ = gitopsCmd.MarkFlagRequired("path")
35}
36
37var gitopsCmd = &cobra.Command{
38 Use: "gitops",
39 Short: "Proxy the in-cluster registry and push the GitOps manifests artifact",
40 PreRunE: func(cmd *cobra.Command, args []string) error {
41 if err := validateClusterFlags(); err != nil {
42 return err
43 }
44 if _, err := exec.LookPath("flux"); err != nil {
45 return fmt.Errorf("find flux CLI: %w", err)
46 }
47 return nil
48 },
49 RunE: runGitopsPush,
50}
51
52func runGitopsPush(cmd *cobra.Command, args []string) error {
53 manifestPath, err := filepath.Abs(gitopsPath)
54 if err != nil {
55 return fmt.Errorf("resolve path %q: %w", gitopsPath, err)
56 }
57
58 connectCtx, cancel := context.WithTimeout(cmd.Context(), connectTimeout)
59 defer cancel()
60
61 hostAddr, err := cluster.LoadHost(hostsFile, host)
62 if err != nil {
63 return fmt.Errorf("load host: %w", err)
64 }
65
66 conn, err := cluster.Connect(cluster.SSHConfig{
67 Host: hostAddr,
68 User: sshUser,
69 KeyPath: sshKey,
70 KnownHostsPath: sshKnownHosts,
71 Timeout: connectTimeout,
72 })
73 if err != nil {
74 return fmt.Errorf("connect to cluster: %w", err)
75 }
76 defer conn.Close()
77
78 tunnel, err := conn.Forward(connectCtx, cluster.ServiceConfig{
79 Namespace: registryNamespace,
80 Name: registryService,
81 Port: registryPort,
82 })
83 if err != nil {
84 return fmt.Errorf("forward registry: %w", err)
85 }
86
87 artifactURL := fmt.Sprintf("oci://%s/%s:%s", tunnel.LocalAddr, gitopsRepository, gitopsTag)
88 args = []string{
89 "push",
90 "artifact",
91 artifactURL,
92 "--path", manifestPath,
93 "--insecure-registry",
94 }
95
96 log.Infof("pushing %s from %s", artifactURL, manifestPath)
97
98 output, err := fluxOutput(cmd.Context(), args...)
99 if err != nil {
100 return fmt.Errorf("push artifact: %w\n%s", err, strings.TrimSpace(string(output)))
101 }
102
103 if trimmed := strings.TrimSpace(string(output)); trimmed != "" {
104 log.Info(trimmed)
105 }
106
107 requestedAt := time.Now().UTC().Format(time.RFC3339Nano)
108 log.Infof("triggering Flux sync for %s/%s", fluxNamespace, fluxKustomization)
109
110 output, err = conn.RunCommandContext(
111 cmd.Context(),
112 fmt.Sprintf(
113 "kubectl annotate --overwrite -n %s ocirepository.source.toolkit.fluxcd.io/%s reconcile.fluxcd.io/requestedAt=%q && kubectl annotate --overwrite -n %s kustomization.kustomize.toolkit.fluxcd.io/%s reconcile.fluxcd.io/requestedAt=%q",
114 fluxNamespace, fluxSource, requestedAt,
115 fluxNamespace, fluxKustomization, requestedAt,
116 ),
117 )
118 if err != nil {
119 return fmt.Errorf("trigger flux sync: %w", err)
120 }
121
122 if trimmed := strings.TrimSpace(string(output)); trimmed != "" {
123 log.Info(trimmed)
124 }
125
126 return nil
127}
128func fluxOutput(ctx context.Context, args ...string) ([]byte, error) {
129 fluxCmd := exec.CommandContext(ctx, "flux", args...)
130 return fluxCmd.CombinedOutput()
131}