@@ -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,57 +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 directly affects the generation of Kubernetes secret resources, which require the CreationTimestamp field to be omitted or a valid string.
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+ }
78+
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+ )
91+
92+ buf := bytes .NewBuffer ([]byte {})
93+ err = serializer .Encode (unstructuredObj , buf )
94+ if err != nil {
95+ return nil , err
96+ }
5897
59- func initSecretTemplate () error {
60- var err error
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+ }
61104
62- secretTemplate , err = template .ParseFS (templateFS , "template/akita-secret.tmpl" )
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 )
63112 if err != nil {
64- return cmderr. AkitaErr { Err : errors . Wrap ( err , "failed to parse secret template" )}
113+ return nil , err
65114 }
66115
67- return nil
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 }
87-
88132 defer secretFile .Close ()
89133
90- buf := new (bytes.Buffer )
91-
92- err = secretTemplate .Execute (buf , input )
134+ _ , err = secretFile .Write (secret )
93135 if err != nil {
94136 return "" , cmderr.AkitaErr {Err : errors .Wrap (err , "failed to generate template" )}
95137 }
96138
97- _ , err = secretFile .Write (buf .Bytes ())
98- if err != nil {
99- return "" , cmderr.AkitaErr {Err : errors .Wrap (err , "failed to read generated secret file" )}
100- }
101-
102- return buf .String (), nil
139+ return string (secret ), nil
103140}
104141
105142// Creates a file at the give path to be used for storing of the generated Secret config
0 commit comments