Skip to content
This repository was archived by the owner on Jul 10, 2024. It is now read-only.

Commit 3d5e896

Browse files
committed
Use k8 API to generate secret resource
1 parent edb4246 commit 3d5e896

File tree

4 files changed

+199
-65
lines changed

4 files changed

+199
-65
lines changed

cmd/internal/kube/secret.go

Lines changed: 74 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,26 @@ package kube
22

33
import (
44
"bytes"
5-
"encoding/base64"
5+
"encoding/json"
6+
"github.com/akitasoftware/akita-cli/printer"
7+
"github.com/akitasoftware/akita-cli/telemetry"
8+
"github.com/ghodss/yaml"
9+
v1 "k8s.io/api/core/v1"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
12+
"k8s.io/apimachinery/pkg/runtime"
13+
k8_json "k8s.io/apimachinery/pkg/runtime/serializer/json"
614
"os"
715
"path/filepath"
8-
"text/template"
9-
10-
"github.com/akitasoftware/akita-cli/telemetry"
1116

1217
"github.com/akitasoftware/akita-cli/cmd/internal/cmderr"
13-
"github.com/akitasoftware/akita-cli/printer"
1418
"github.com/pkg/errors"
1519
"github.com/spf13/cobra"
1620
)
1721

1822
var (
1923
outputFlag string
2024
namespaceFlag string
21-
// Store a parsed representation of /template/akita-secret.tmpl
22-
secretTemplate *template.Template
2325
)
2426

2527
var secretCmd = &cobra.Command{
@@ -49,56 +51,92 @@ var secretCmd = &cobra.Command{
4951
},
5052
}
5153

52-
// Represents the input used by secretTemplate
53-
type secretTemplateInput struct {
54-
Namespace string
55-
APIKey string
56-
APISecret string
57-
}
54+
/*
55+
XXX: Kuberenetes Go API package currently has issues with valid serialization.
56+
The ObjectMeta field's CreationTimestamp field is improperly serialized as null when it should be omitted entirely if it is a zero value.
57+
This shouldn't cause any issues applying the secret, but it does cause issues for any tools that depend on valid yaml objects (such as linting tools)
58+
See: https://github.com/kubernetes/kubernetes/issues/109427
59+
60+
Here, I've manually filtered out the CreationTimestamp field from the serialized object to work around this issue.
61+
*/
62+
func buildSecretConfiguration(namespace, apiKey, apiSecret string) ([]byte, error) {
63+
secret := &v1.Secret{
64+
TypeMeta: metav1.TypeMeta{
65+
APIVersion: "v1",
66+
Kind: "Secret",
67+
},
68+
ObjectMeta: metav1.ObjectMeta{
69+
Name: "akita-secrets",
70+
Namespace: namespace,
71+
},
72+
Type: v1.SecretTypeOpaque,
73+
Data: map[string][]byte{
74+
"akita-api-key": []byte(apiKey),
75+
"akita-api-secret": []byte(apiSecret),
76+
},
77+
}
5878

59-
func initSecretTemplate() error {
60-
var err error
79+
unstructuredSecret, err := runtime.DefaultUnstructuredConverter.ToUnstructured(secret)
80+
if err != nil {
81+
return nil, err
82+
}
83+
84+
unstructuredObj := &unstructured.Unstructured{Object: unstructuredSecret}
85+
serializer := k8_json.NewSerializerWithOptions(
86+
k8_json.DefaultMetaFactory,
87+
nil,
88+
nil,
89+
k8_json.SerializerOptions{Yaml: false, Pretty: false, Strict: true},
90+
)
6191

62-
secretTemplate, err = template.ParseFS(templateFS, "template/akita-secret.tmpl")
92+
buf := bytes.NewBuffer([]byte{})
93+
err = serializer.Encode(unstructuredObj, buf)
6394
if err != nil {
64-
return cmderr.AkitaErr{Err: errors.Wrap(err, "failed to parse secret template")}
95+
return nil, err
6596
}
6697

67-
return nil
98+
// HACK: Manually filter out the CreationTimestamp field from the serialized object
99+
objMap := make(map[string]interface{})
100+
err = json.Unmarshal(buf.Bytes(), &objMap)
101+
if err != nil {
102+
return nil, err
103+
}
104+
105+
if _, ok := objMap["metadata"]; ok {
106+
metadataMap := objMap["metadata"].(map[string]interface{})
107+
delete(metadataMap, "creationTimestamp")
108+
}
109+
110+
// Re-serialize the object
111+
fixedJSON, err := json.Marshal(objMap)
112+
if err != nil {
113+
return nil, err
114+
}
115+
116+
return yaml.JSONToYAML(fixedJSON)
68117
}
69118

70119
// Generates a Kubernetes secret config file for Akita
71-
// On success, the generated output is returned as a string.
72-
func handleSecretGeneration(namespace, key, secret, output string) (string, error) {
73-
if err := initSecretTemplate(); err != nil {
74-
return "", err
75-
}
120+
func handleSecretGeneration(namespace, apiKey, apiSecret, output string) (string, error) {
76121

77-
input := secretTemplateInput{
78-
Namespace: namespace,
79-
APIKey: base64.StdEncoding.EncodeToString([]byte(key)),
80-
APISecret: base64.StdEncoding.EncodeToString([]byte(secret)),
122+
secret, err := buildSecretConfiguration(namespace, apiKey, apiSecret)
123+
if err != nil {
124+
return "", cmderr.AkitaErr{Err: errors.Wrap(err, "failed to generate Kubernetes secret")}
81125
}
82126

127+
// Serialize the secret to YAML
83128
secretFile, err := createSecretFile(output)
84129
if err != nil {
85130
return "", cmderr.AkitaErr{Err: errors.Wrap(err, "failed to create output file")}
86131
}
87132
defer secretFile.Close()
88133

89-
buf := bytes.NewBuffer([]byte{})
90-
91-
err = secretTemplate.Execute(buf, input)
134+
_, err = secretFile.Write(secret)
92135
if err != nil {
93136
return "", cmderr.AkitaErr{Err: errors.Wrap(err, "failed to generate template")}
94137
}
95138

96-
_, err = secretFile.Write(buf.Bytes())
97-
if err != nil {
98-
return "", cmderr.AkitaErr{Err: errors.Wrap(err, "failed to read generated secret file")}
99-
}
100-
101-
return buf.String(), nil
139+
return string(secret), nil
102140
}
103141

104142
// Creates a file at the give path to be used for storing of the generated Secret config

cmd/internal/kube/secret_test.go

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ package kube
22

33
import (
44
_ "embed"
5+
"encoding/json"
56
"github.com/stretchr/testify/assert"
7+
v1 "k8s.io/api/core/v1"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
69
"os"
710
"path/filepath"
11+
"sigs.k8s.io/yaml"
812
"testing"
913
)
1014

@@ -23,17 +27,50 @@ func Test_secretGeneration(t *testing.T) {
2327
actualOutput := filepath.Join(dir, "akita-secret.yml")
2428

2529
// WHEN
26-
output, err := handleSecretGeneration(namespace, key, secret, actualOutput)
30+
result, err := handleSecretGeneration(namespace, key, secret, actualOutput)
2731
if err != nil {
2832
t.Errorf("Unexpected error: %s", err)
2933
}
3034

31-
generatedFile, err := os.ReadFile(actualOutput)
35+
// THEN
36+
data, err := os.ReadFile(actualOutput)
3237
if err != nil {
33-
t.Errorf("Failed to read generated generatedFile: %v", err)
38+
t.Errorf("Failed to read generated data: %v", err)
3439
}
3540

36-
// THEN
37-
assert.Equal(t, string(testAkitaSecretYAML), string(generatedFile))
38-
assert.Equal(t, string(testAkitaSecretYAML), output)
41+
convert := func(yamlBytes []byte) (v1.Secret, error) {
42+
var result v1.Secret
43+
44+
jsonData, err := yaml.YAMLToJSONStrict(yamlBytes)
45+
if err != nil {
46+
return result, err
47+
}
48+
49+
var parsedSecret v1.Secret
50+
err = json.Unmarshal(jsonData, &parsedSecret)
51+
52+
return parsedSecret, err
53+
}
54+
55+
file, err := convert(data)
56+
output, err := convert([]byte(result))
57+
58+
expected := v1.Secret{
59+
TypeMeta: metav1.TypeMeta{
60+
APIVersion: "v1",
61+
Kind: "Secret",
62+
},
63+
ObjectMeta: metav1.ObjectMeta{
64+
Name: "akita-secrets",
65+
Namespace: namespace,
66+
},
67+
Type: v1.SecretTypeOpaque,
68+
Data: map[string][]byte{
69+
"akita-api-key": []byte(key),
70+
"akita-api-secret": []byte(secret),
71+
},
72+
}
73+
74+
assert.Equal(t, expected, file)
75+
assert.Equal(t, expected, output)
3976
}

go.mod

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ require (
1919
github.com/aws/smithy-go v1.13.4
2020
github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8
2121
github.com/gdamore/tcell/v2 v2.1.0
22+
github.com/ghodss/yaml v1.0.0
2223
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f
2324
github.com/golang/mock v1.3.1
2425
github.com/golang/protobuf v1.5.2
25-
github.com/google/go-cmp v0.5.8
26+
github.com/google/go-cmp v0.5.9
2627
github.com/google/gopacket v1.1.19
2728
github.com/google/martian/v3 v3.0.1
2829
github.com/google/uuid v1.3.0
@@ -38,12 +39,15 @@ require (
3839
github.com/spf13/cobra v1.1.3
3940
github.com/spf13/pflag v1.0.5
4041
github.com/spf13/viper v1.7.1
41-
github.com/stretchr/testify v1.7.1
42+
github.com/stretchr/testify v1.8.0
4243
github.com/yudai/gojsondiff v1.0.0
4344
golang.org/x/exp v0.0.0-20220428152302-39d4317da171
44-
golang.org/x/term v0.1.0
45-
golang.org/x/text v0.4.0
45+
golang.org/x/term v0.5.0
46+
golang.org/x/text v0.7.0
4647
gopkg.in/yaml.v2 v2.4.0
48+
k8s.io/api v0.26.3
49+
k8s.io/apimachinery v0.26.3
50+
sigs.k8s.io/yaml v1.3.0
4751
)
4852

4953
require (
@@ -61,13 +65,17 @@ require (
6165
github.com/dukex/mixpanel v1.0.1 // indirect
6266
github.com/fsnotify/fsnotify v1.4.9 // indirect
6367
github.com/gdamore/encoding v1.0.0 // indirect
68+
github.com/go-logr/logr v1.2.3 // indirect
69+
github.com/gogo/protobuf v1.3.2 // indirect
6470
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
71+
github.com/google/gofuzz v1.1.0 // indirect
6572
github.com/hashicorp/errwrap v1.0.0 // indirect
6673
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
6774
github.com/hashicorp/go-multierror v1.1.1 // indirect
6875
github.com/hashicorp/hcl v1.0.0 // indirect
6976
github.com/inconshreveable/mousetrap v1.0.0 // indirect
7077
github.com/jmespath/go-jmespath v0.4.0 // indirect
78+
github.com/json-iterator/go v1.1.12 // indirect
7179
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
7280
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
7381
github.com/magiconair/properties v1.8.1 // indirect
@@ -76,6 +84,8 @@ require (
7684
github.com/mattn/go-runewidth v0.0.10 // indirect
7785
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
7886
github.com/mitchellh/mapstructure v1.1.2 // indirect
87+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
88+
github.com/modern-go/reflect2 v1.0.2 // indirect
7989
github.com/pelletier/go-toml v1.2.0 // indirect
8090
github.com/pmezard/go-difflib v1.0.0 // indirect
8191
github.com/rivo/uniseg v0.2.0 // indirect
@@ -87,11 +97,16 @@ require (
8797
github.com/spf13/jwalterweatherman v1.0.0 // indirect
8898
github.com/subosito/gotenv v1.2.0 // indirect
8999
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
90-
golang.org/x/net v0.1.0 // indirect
91-
golang.org/x/sys v0.1.0 // indirect
92-
google.golang.org/protobuf v1.27.1 // indirect
100+
golang.org/x/net v0.7.0 // indirect
101+
golang.org/x/sys v0.5.0 // indirect
102+
google.golang.org/protobuf v1.28.1 // indirect
103+
gopkg.in/inf.v0 v0.9.1 // indirect
93104
gopkg.in/ini.v1 v1.51.0 // indirect
94-
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
105+
gopkg.in/yaml.v3 v3.0.1 // indirect
106+
k8s.io/klog/v2 v2.80.1 // indirect
107+
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect
108+
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
109+
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
95110
)
96111

97112
replace (

0 commit comments

Comments
 (0)