Skip to content

Commit 5196840

Browse files
feat: process embed kots release (#17)
* feat: process embed kots release start to process the embed kots release. the kots release is embed inside the helmbin binary as a tar.gz file, we need to extract it and create a config map called kotsadm-application-metadata in the default namespace, this configmap contains the kots application cr and is used as means of interface customisation by the kotsadm. * chore: make lint happy fixing a pair of missing returned error checks.
1 parent 8c3ad96 commit 5196840

File tree

2 files changed

+146
-0
lines changed

2 files changed

+146
-0
lines changed

pkg/cli/run.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ func runController(ctx context.Context, opts config.K0sControllerOptions) error
5757
manager.Add(&controller.Helm{
5858
Options: opts.CLIOptions,
5959
})
60+
manager.Add(&controller.KotsRelease{
61+
Options: opts.CLIOptions,
62+
})
6063
if err := manager.Init(ctx); err != nil {
6164
return fmt.Errorf("failed to initialize manager: %w", err)
6265
}

pkg/controller/kotsrelease.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package controller
2+
3+
import (
4+
"archive/tar"
5+
"bytes"
6+
"compress/gzip"
7+
"context"
8+
"debug/elf"
9+
"fmt"
10+
"io"
11+
"os"
12+
13+
"github.com/sirupsen/logrus"
14+
corev1 "k8s.io/api/core/v1"
15+
"k8s.io/apimachinery/pkg/api/errors"
16+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17+
"k8s.io/client-go/kubernetes"
18+
kconfig "sigs.k8s.io/controller-runtime/pkg/client/config"
19+
20+
"github.com/replicatedhq/helmbin/pkg/config"
21+
)
22+
23+
// KotsRelease implement the component interface to run the KotsRelease controller. This controller
24+
// inspects for assets embed into the binary and apply them.
25+
type KotsRelease struct {
26+
Options config.CLIOptions
27+
app []byte
28+
log logrus.FieldLogger
29+
}
30+
31+
// Init initializes the KotsRelease controller. Reads the "sec_bundle" section of the elf binary if
32+
// it exists. The "sec_bundle" section is expected to contain a tar.gz archive of the kots release
33+
// yaml files. The embed process of this section does not happen here but it is something that is
34+
// executed by the release build process.
35+
func (k *KotsRelease) Init(_ context.Context) error {
36+
k.log = logrus.WithField("component", "kots-release")
37+
exe, err := os.Executable()
38+
if err != nil {
39+
return fmt.Errorf("failed to get executable path: %w", err)
40+
}
41+
fpbin, err := elf.Open(exe)
42+
if err != nil {
43+
return fmt.Errorf("failed to open %s binary: %w", os.Args[0], err)
44+
}
45+
defer func() {
46+
_ = fpbin.Close()
47+
}()
48+
section := fpbin.Section("sec_bundle")
49+
if section == nil {
50+
k.log.Infof("No kots release bundle found inside the binary.")
51+
return nil
52+
}
53+
k.log.Infof("Found embedded kots release bundle, inspecting it.")
54+
if k.app, err = k.processBundleSection(section); err != nil {
55+
return fmt.Errorf("failed to process bundle section: %w", err)
56+
}
57+
return nil
58+
}
59+
60+
// processBundleSection reads the provided elf section. This section is expected to contain a tar.gz file
61+
// with yamls inside, this function extracts the tar.gz content and searches for an Application CR yaml,
62+
// if found returns its content. This function returns nil,nil if no Application CR is found.
63+
func (k *KotsRelease) processBundleSection(section *elf.Section) ([]byte, error) {
64+
gzr, err := gzip.NewReader(section.Open())
65+
if err != nil {
66+
return nil, fmt.Errorf("failed to create gzip reader for sec_bundle section: %w", err)
67+
}
68+
defer func() {
69+
_ = gzr.Close()
70+
}()
71+
tr := tar.NewReader(gzr)
72+
for {
73+
header, err := tr.Next()
74+
switch {
75+
case err == io.EOF:
76+
return nil, nil
77+
case err != nil:
78+
return nil, fmt.Errorf("failed to read tar gz file: %w", err)
79+
case header == nil:
80+
continue
81+
}
82+
if header.Typeflag != tar.TypeReg {
83+
continue
84+
}
85+
content := bytes.NewBuffer(nil)
86+
if _, err := io.Copy(content, tr); err != nil {
87+
return nil, fmt.Errorf("failed to copy binary out of tar: %w", err)
88+
}
89+
if !bytes.Contains(content.Bytes(), []byte("apiVersion: kots.io/v1beta1")) {
90+
continue
91+
}
92+
if !bytes.Contains(content.Bytes(), []byte("kind: Application")) {
93+
continue
94+
}
95+
k.log.Infof("Kots Application definition found on file %s", header.Name)
96+
return content.Bytes(), nil
97+
}
98+
}
99+
100+
// kubeclient builds a kubernetes client.
101+
func (k *KotsRelease) kubeclient() (kubernetes.Interface, error) {
102+
cfg, err := kconfig.GetConfig()
103+
if err != nil {
104+
return nil, fmt.Errorf("failed to get kubeconfig: %w", err)
105+
}
106+
return kubernetes.NewForConfig(cfg)
107+
}
108+
109+
// Start starts the KotsRelease controller. It creates or updates the kotsadm-application-metadata configmap in the
110+
// default namespace and then finishes. If no kots release bundle is found inside the binary, this is a no-op.
111+
func (k *KotsRelease) Start(ctx context.Context) error {
112+
if k.app == nil {
113+
k.log.Infof("No kots release bundle found, skipping")
114+
return nil
115+
}
116+
cli, err := k.kubeclient()
117+
if err != nil {
118+
return fmt.Errorf("failed to create kube client: %w", err)
119+
}
120+
cm, err := cli.CoreV1().ConfigMaps("default").Get(ctx, "kotsadm-application-metadata", metav1.GetOptions{})
121+
if err != nil {
122+
if !errors.IsNotFound(err) {
123+
return fmt.Errorf("failed to get kotsadm-application configmap: %w", err)
124+
}
125+
data := map[string]string{"application.yaml": string(k.app)}
126+
meta := metav1.ObjectMeta{Name: "kotsadm-application-metadata", Namespace: "default"}
127+
cm = &corev1.ConfigMap{ObjectMeta: meta, Data: data}
128+
if _, err := cli.CoreV1().ConfigMaps("default").Create(ctx, cm, metav1.CreateOptions{}); err != nil {
129+
return fmt.Errorf("failed to create kotsadm-application configmap: %w", err)
130+
}
131+
return nil
132+
}
133+
cm.Data["application.yaml"] = string(k.app)
134+
if _, err := cli.CoreV1().ConfigMaps("default").Update(ctx, cm, metav1.UpdateOptions{}); err != nil {
135+
return fmt.Errorf("failed to update kotsadm-application configmap: %w", err)
136+
}
137+
return nil
138+
}
139+
140+
// Stop stops the KotsRelease controller
141+
func (k *KotsRelease) Stop() error {
142+
return nil
143+
}

0 commit comments

Comments
 (0)