Skip to content

Commit 0a681ed

Browse files
feat: applying kotsadm customization (#26)
to keep this backward compatible with the previous version of the helmbin we need to inspect the binary to see if there is any kind of kotsadm customization and if found apply them.
1 parent 1e44f25 commit 0a681ed

File tree

4 files changed

+148
-19
lines changed

4 files changed

+148
-19
lines changed

pkg/addons/adminconsole/adminconsole.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"helm.sh/helm/v3/pkg/chart/loader"
1515
"helm.sh/helm/v3/pkg/cli"
1616
"helm.sh/helm/v3/pkg/release"
17+
"sigs.k8s.io/controller-runtime/pkg/client"
1718

1819
"github.com/replicatedhq/helmvm/pkg/addons/adminconsole/charts"
1920
)
@@ -33,10 +34,11 @@ var helmValues = map[string]interface{}{
3334
}
3435

3536
type AdminConsole struct {
36-
config *action.Configuration
37-
logger action.DebugLog
38-
namespace string
39-
prompt bool
37+
customization AdminConsoleCustomization
38+
config *action.Configuration
39+
logger action.DebugLog
40+
namespace string
41+
prompt bool
4042
}
4143

4244
func (a *AdminConsole) askPassword() (string, error) {
@@ -96,25 +98,22 @@ func (a *AdminConsole) Apply(ctx context.Context) error {
9698
if _, err := act.RunWithContext(ctx, hchart, helmValues); err != nil {
9799
return fmt.Errorf("unable to install chart: %w", err)
98100
}
99-
return nil
101+
return a.customization.apply(ctx)
100102
}
101103

102104
a.logger("Admin Console already installed on the cluster, checking version.")
103105
installedVersion := fmt.Sprintf("v%s", release.Chart.Metadata.Version)
104106
if out := semver.Compare(installedVersion, version); out > 0 {
105107
return fmt.Errorf("unable to downgrade from %s to %s", installedVersion, version)
106-
} else if out == 0 {
107-
a.logger("Admin Console version %s already installed, skipping", version)
108-
return nil
109108
}
110109

111-
a.logger("Upgrading Admin Console from %s to %s", installedVersion, version)
110+
a.logger("Updating Admin Console from %s to %s", installedVersion, version)
112111
act := action.NewUpgrade(a.config)
113112
act.Namespace = a.namespace
114113
if _, err := act.RunWithContext(ctx, releaseName, hchart, helmValues); err != nil {
115114
return fmt.Errorf("unable to upgrade chart: %w", err)
116115
}
117-
return nil
116+
return a.customization.apply(ctx)
118117
}
119118

120119
func (a *AdminConsole) Latest() (string, error) {
@@ -160,17 +159,18 @@ func (a *AdminConsole) installedRelease(ctx context.Context) (*release.Release,
160159
return releases[0], nil
161160
}
162161

163-
func New(namespace string, prompt bool, logger action.DebugLog) (*AdminConsole, error) {
162+
func New(ns string, prompt bool, kcli client.Client, log action.DebugLog) (*AdminConsole, error) {
164163
env := cli.New()
165-
env.SetNamespace(namespace)
164+
env.SetNamespace(ns)
166165
config := &action.Configuration{}
167-
if err := config.Init(env.RESTClientGetter(), namespace, "", logger); err != nil {
166+
if err := config.Init(env.RESTClientGetter(), ns, "", log); err != nil {
168167
return nil, fmt.Errorf("unable to init configuration: %w", err)
169168
}
170169
return &AdminConsole{
171-
namespace: namespace,
172-
config: config,
173-
logger: logger,
174-
prompt: prompt,
170+
namespace: ns,
171+
config: config,
172+
logger: log,
173+
prompt: prompt,
174+
customization: AdminConsoleCustomization{kcli},
175175
}, nil
176176
}

pkg/addons/adminconsole/customize.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package adminconsole
2+
3+
import (
4+
"archive/tar"
5+
"bytes"
6+
"compress/gzip"
7+
"context"
8+
"debug/elf"
9+
"fmt"
10+
"io"
11+
"os"
12+
"runtime"
13+
14+
"github.com/sirupsen/logrus"
15+
corev1 "k8s.io/api/core/v1"
16+
"k8s.io/apimachinery/pkg/api/errors"
17+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18+
"sigs.k8s.io/controller-runtime/pkg/client"
19+
)
20+
21+
// AdminConsoleCustomization is a struct that contains the actions to create and update
22+
// the admin console customization found inside the binary. This is necessary for
23+
// backwards compatibility with older versions of helmvm.
24+
type AdminConsoleCustomization struct {
25+
kubeclient client.Client
26+
}
27+
28+
// extractCustomization will extract the customization from the binary if it exists.
29+
// If it does not exist, it will return nil, nil. The customization is expected to
30+
// be found in the sec_bundle section of the binary.
31+
func (a *AdminConsoleCustomization) extractCustomization() ([]byte, error) {
32+
exe, err := os.Executable()
33+
if err != nil {
34+
return nil, err
35+
}
36+
fpbin, err := elf.Open(exe)
37+
if err != nil {
38+
return nil, err
39+
}
40+
defer fpbin.Close()
41+
section := fpbin.Section("sec_bundle")
42+
if section == nil {
43+
return nil, nil
44+
}
45+
return a.processSection(section)
46+
}
47+
48+
// processSection searches the provided elf section for a gzip compressed tar archive.
49+
// If it finds one, it will extract the contents and return the kots.io Application
50+
// object as a byte slice.
51+
func (a *AdminConsoleCustomization) processSection(section *elf.Section) ([]byte, error) {
52+
gzr, err := gzip.NewReader(section.Open())
53+
if err != nil {
54+
return nil, err
55+
}
56+
defer gzr.Close()
57+
tr := tar.NewReader(gzr)
58+
for {
59+
header, err := tr.Next()
60+
switch {
61+
case err == io.EOF:
62+
return nil, nil
63+
case err != nil:
64+
return nil, fmt.Errorf("unable to read tgz file: %w", err)
65+
case header == nil:
66+
continue
67+
}
68+
if header.Typeflag != tar.TypeReg {
69+
continue
70+
}
71+
content := bytes.NewBuffer(nil)
72+
if _, err := io.Copy(content, tr); err != nil {
73+
return nil, fmt.Errorf("unable to copy file out of tar: %w", err)
74+
}
75+
if !bytes.Contains(content.Bytes(), []byte("apiVersion: kots.io/v1beta1")) {
76+
continue
77+
}
78+
if !bytes.Contains(content.Bytes(), []byte("kind: Application")) {
79+
continue
80+
}
81+
return content.Bytes(), nil
82+
}
83+
}
84+
85+
// apply will attempt to read the helmvm binary and extract the kotsadm portal
86+
// customization from it. If it finds one, it will apply it to the cluster.
87+
func (a *AdminConsoleCustomization) apply(ctx context.Context) error {
88+
logrus.Infof("Applying admin console customization")
89+
if runtime.GOOS != "linux" {
90+
logrus.Infof("Skipping admin console customization on %s", runtime.GOOS)
91+
return nil
92+
}
93+
cust, err := a.extractCustomization()
94+
if err != nil {
95+
return fmt.Errorf("unable to extract customization from binary: %w", err)
96+
}
97+
if cust == nil {
98+
logrus.Infof("No admin console customization found")
99+
return nil
100+
}
101+
logrus.Infof("Admin console customization found")
102+
nsn := client.ObjectKey{Namespace: "helmvm", Name: "kotsadm-application-metadata"}
103+
var cm corev1.ConfigMap
104+
if err := a.kubeclient.Get(ctx, nsn, &cm); err != nil {
105+
if !errors.IsNotFound(err) {
106+
return fmt.Errorf("unable to get kotsadm-application configmap: %w", err)
107+
}
108+
logrus.Infof("Creating admin console customization config map")
109+
cm = corev1.ConfigMap{
110+
ObjectMeta: metav1.ObjectMeta{
111+
Namespace: nsn.Namespace,
112+
Name: nsn.Name,
113+
},
114+
Data: map[string]string{
115+
"application.yaml": string(cust),
116+
},
117+
}
118+
if err := a.kubeclient.Create(ctx, &cm); err != nil {
119+
return fmt.Errorf("unable to create kotsadm-application configmap: %w", err)
120+
}
121+
return nil
122+
}
123+
logrus.Infof("Updating admin console customization config map")
124+
cm.Data["application.yaml"] = string(cust)
125+
if err := a.kubeclient.Update(ctx, &cm); err != nil {
126+
return fmt.Errorf("unable to update kotsadm-application configmap: %w", err)
127+
}
128+
return nil
129+
}

pkg/addons/applier.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ func NewApplier(prompt bool) (*Applier, error) {
101101
}
102102
applier.addons["openebs"] = obs
103103
logger = logrus.WithField("addon", "adminconsole")
104-
aconsole, err := adminconsole.New("helmvm", prompt, logger.Infof)
104+
aconsole, err := adminconsole.New("helmvm", prompt, kubecli, logger.Infof)
105105
if err != nil {
106106
return nil, fmt.Errorf("unable to create admin console addon: %w", err)
107107
}

pkg/addons/openebs/openebs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func (o *OpenEBS) Apply(ctx context.Context) error {
8383
return fmt.Errorf("unable to downgrade from %s to %s", installedVersion, version)
8484
}
8585

86-
o.logger("Upgrading OpenEBS from %s to %s", installedVersion, version)
86+
o.logger("Updating OpenEBS from %s to %s", installedVersion, version)
8787
act := action.NewUpgrade(o.config)
8888
act.Namespace = o.namespace
8989
if _, err := act.RunWithContext(ctx, releaseName, hchart, helmValues); err != nil {

0 commit comments

Comments
 (0)