@@ -2,24 +2,26 @@ package kube
22
33import (
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
1822var (
1923 outputFlag string
2024 namespaceFlag string
21- // Store a parsed representation of /template/akita-secret.tmpl
22- secretTemplate * template.Template
2325)
2426
2527var 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
0 commit comments