kinflate: Update Manifest type for configmap and secrets

- Generic type is now named DataSources to remove ambiguity,
- NamePrefix is now just Name, since that's what it is,
- Secret have been split in GenericSecret and TLSSecret,

The code, test and examples have been updated to work with these new
types.
This commit is contained in:
Antoine Pelisse 2018-02-02 11:46:25 -08:00
parent 33bc00af62
commit b6e4fe00ee
7 changed files with 247 additions and 143 deletions

View File

@ -100,11 +100,17 @@ type Manifest struct {
// and Overlays fields.
Configmaps []ConfigMap `json:"configmaps,omitempty" yaml:"configmaps,omitempty"`
// List of secrets to generate from secret sources.
// List of generic secrets to generate from secret sources.
// Base/overlay concept doesn't apply to this field.
// If a secret want to have a base and an overlay, it should go to Bases and
// Overlays fields.
Secrets []Secret `json:"secrets,omitempty" yaml:"secrets,omitempty"`
GenericSecrets []GenericSecret `json:"genericSecrets,omitempty" yaml:"genericSecrets,omitempty"`
// List of TLS secrets to generate from secret sources.
// Base/overlay concept doesn't apply to this field.
// If a secret want to have a base and an overlay, it should go to Bases and
// Overlays fields.
TLSSecrets []TLSSecret `json:"tlsSecrets,omitempty" yaml:"tlsSecrets,omitempty"`
// Whether prune resources not defined in Kube-manifest.yaml, similar to
// `kubectl apply --prune` behavior.
@ -122,41 +128,43 @@ type Manifest struct {
// ConfigMap contains the metadata of how to generate a configmap.
type ConfigMap struct {
// The type of the configmap. e.g. `env`, `file`, `literal`.
Type string `json:"type,omitempty" yaml:"type,omitempty"`
// Name prefix of the configmap.
// The full name should be Manifest.NamePrefix + Configmap.NamePrefix +
// Name of the configmap.
// The full name should be Manifest.NamePrefix + Configmap.Name +
// hash(content of configmap).
NamePrefix string `json:"namePrefix,omitempty" yaml:"namePrefix,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
// Generic source for configmap, it could of one of `env`, `file`, `literal`
Generic `json:",inline,omitempty" yaml:",inline,omitempty"`
// DataSources for configmap.
DataSources `json:",inline,omitempty" yaml:",inline,omitempty"`
}
// Secret contains the metadata of how to generate a secret.
// Only one of source or tls can be set.
type Secret struct {
// The type of the secret. e.g. `generic` and `tls`.
Type string `json:"type,omitempty" yaml:"type,omitempty"`
// Name prefix of the secret.
// The full name should be Manifest.NamePrefix + Secret.NamePrefix +
// GenericSecret contains the metadata of how to generate a generic secret.
type GenericSecret struct {
// Name of the secret.
// The full name should be Manifest.NamePrefix + GenericSecret.Name +
// hash(content of secret).
NamePrefix string `json:"namePrefix,omitempty" yaml:"namePrefix,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
// Generic source for secret, it could of one of `env`, `file`, `literal`
Generic `json:",inline,omitempty" yaml:",inline,omitempty"`
// TLS secret.
TLS *TLS `json:"tls,omitempty" yaml:"tls,omitempty"`
// TODO: support more secret types, e.g. DockerRegistry
// DataSources for secret.
DataSources `json:",inline,omitempty" yaml:",inline,omitempty"`
}
// Generic contains some generic sources for configmap or secret.
// TLSSecret contains the metadata of how to generate a TLS secret.
type TLSSecret struct {
// Name of the secret
// The full name should be Manifest.NamePrefix + TLSSecret.Name +
// hash(content of secret).
Name string `json:"name,omitempty" yaml:"name,omitempty"`
// Path to PEM encoded public key certificate.
CertFile string `json:"certFile,omitempty" yaml:"certFile,omitempty"`
// Path to private key associated with given certificate.
KeyFile string `json:"keyFile,omitempty" yaml:"keyFile,omitempty"`
}
// DataSources contains some generic sources for configmap or secret.
// Only one field can be set.
type Generic struct {
type DataSources struct {
// LiteralSources is a list of literal sources.
// Each literal source should be a key and literal value,
// e.g. `somekey=somevalue`
@ -177,12 +185,3 @@ type Generic struct {
// i.e. a Docker .env file or a .ini file.
EnvSource string `json:"env,omitempty" yaml:"env,omitempty"`
}
// TLS contains cert and key paths.
type TLS struct {
// Path to PEM encoded public key certificate.
CertFile string `json:"certFile,omitempty" yaml:"certFile,omitempty"`
// Path to private key associated with given certificate.
KeyFile string `json:"keyFile,omitempty" yaml:"keyFile,omitempty"`
}

View File

@ -26,8 +26,8 @@ func adjustPathsForManifest(m *manifest.Manifest, pathToDir []string) {
m.Resources = adjustPaths(m.Resources, pathToDir)
m.Patches = adjustPaths(m.Patches, pathToDir)
m.Configmaps = adjustPathForConfigMaps(m.Configmaps, pathToDir)
m.Secrets = adjustPathForSecrets(m.Secrets, pathToDir)
m.GenericSecrets = adjustPathForGenericSecrets(m.GenericSecrets, pathToDir)
m.TLSSecrets = adjustPathForTLSSecrets(m.TLSSecrets, pathToDir)
}
func adjustPathForConfigMaps(cms []manifest.ConfigMap, prefix []string) []manifest.ConfigMap {
@ -44,7 +44,7 @@ func adjustPathForConfigMaps(cms []manifest.ConfigMap, prefix []string) []manife
return cms
}
func adjustPathForSecrets(secrets []manifest.Secret, prefix []string) []manifest.Secret {
func adjustPathForGenericSecrets(secrets []manifest.GenericSecret, prefix []string) []manifest.GenericSecret {
for i, secret := range secrets {
if len(secret.FileSources) > 0 {
for j, fileSource := range secret.FileSources {
@ -54,10 +54,14 @@ func adjustPathForSecrets(secrets []manifest.Secret, prefix []string) []manifest
if len(secret.EnvSource) > 0 {
secrets[i].EnvSource = adjustPath(secret.EnvSource, prefix)
}
if secret.TLS != nil {
secrets[i].TLS.CertFile = adjustPath(secret.TLS.CertFile, prefix)
secrets[i].TLS.KeyFile = adjustPath(secret.TLS.KeyFile, prefix)
}
}
return secrets
}
func adjustPathForTLSSecrets(secrets []manifest.TLSSecret, prefix []string) []manifest.TLSSecret {
for i, secret := range secrets {
secrets[i].CertFile = adjustPath(secret.CertFile, prefix)
secrets[i].KeyFile = adjustPath(secret.KeyFile, prefix)
}
return secrets
}

View File

@ -45,18 +45,31 @@ func MakeConfigmapAndGenerateName(cm manifest.ConfigMap) (*unstructured.Unstruct
return unstructuredCM, nameWithHash, err
}
// MakeSecretAndGenerateName makes a secret and returns the secret and the name appended with a hash.
func MakeSecretAndGenerateName(secret manifest.Secret) (*unstructured.Unstructured, string, error) {
corev1Secret, err := makeSecret(secret)
// MakeGenericSecretAndGenerateName makes a generic secret and returns the secret and the name appended with a hash.
func MakeGenericSecretAndGenerateName(secret manifest.GenericSecret) (*unstructured.Unstructured, string, error) {
corev1Secret, err := makeGenericSecret(secret)
if err != nil {
return nil, "", err
}
h, err := hash.SecretHash(corev1Secret)
return makeSecretAndGenerateName(corev1Secret, secret.Name)
}
// MakeTLSSecretAndGenerateName makes a generic secret and returns the secret and the name appended with a hash.
func MakeTLSSecretAndGenerateName(secret manifest.TLSSecret) (*unstructured.Unstructured, string, error) {
corev1Secret, err := makeTlsSecret(secret)
if err != nil {
return nil, "", err
}
nameWithHash := fmt.Sprintf("%s-%s", corev1Secret.GetName(), h)
unstructuredCM, err := objectToUnstructured(corev1Secret)
return makeSecretAndGenerateName(corev1Secret, secret.Name)
}
func makeSecretAndGenerateName(secret *corev1.Secret, name string) (*unstructured.Unstructured, string, error) {
h, err := hash.SecretHash(secret)
if err != nil {
return nil, "", err
}
nameWithHash := fmt.Sprintf("%s-%s", name, h)
unstructuredCM, err := objectToUnstructured(secret)
return unstructuredCM, nameWithHash, err
}
@ -74,55 +87,76 @@ func makeConfigMap(cm manifest.ConfigMap) (*corev1.ConfigMap, error) {
corev1cm := &corev1.ConfigMap{}
corev1cm.APIVersion = "v1"
corev1cm.Kind = "ConfigMap"
corev1cm.Name = cm.NamePrefix
corev1cm.Name = cm.Name
corev1cm.Data = map[string]string{}
var err error
switch cm.Type {
case "env":
err = cutil.HandleConfigMapFromEnvFileSource(corev1cm, cm.EnvSource)
case "file":
err = cutil.HandleConfigMapFromFileSources(corev1cm, cm.FileSources)
case "literal":
err = cutil.HandleConfigMapFromLiteralSources(corev1cm, cm.LiteralSources)
default:
err = fmt.Errorf("unknown type of configmap: %v", cm.Type)
if cm.EnvSource != "" {
if err := cutil.HandleConfigMapFromEnvFileSource(corev1cm, cm.EnvSource); err != nil {
return nil, err
}
}
return corev1cm, err
if cm.FileSources != nil {
if err := cutil.HandleConfigMapFromFileSources(corev1cm, cm.FileSources); err != nil {
return nil, err
}
}
if cm.LiteralSources != nil {
if err := cutil.HandleConfigMapFromLiteralSources(corev1cm, cm.LiteralSources); err != nil {
return nil, err
}
}
return corev1cm, nil
}
func makeSecret(secret manifest.Secret) (*corev1.Secret, error) {
func makeGenericSecret(secret manifest.GenericSecret) (*corev1.Secret, error) {
corev1secret := &corev1.Secret{}
corev1secret.APIVersion = "v1"
corev1secret.Kind = "Secret"
corev1secret.Name = secret.NamePrefix
corev1secret.Name = secret.Name
corev1secret.Type = corev1.SecretTypeOpaque
corev1secret.Data = map[string][]byte{}
var err error
switch secret.Type {
case "tls":
if err = validateTLS(secret.TLS.CertFile, secret.TLS.KeyFile); err != nil {
if secret.EnvSource != "" {
if err := cutil.HandleFromEnvFileSource(corev1secret, secret.EnvSource); err != nil {
return nil, err
}
tlsCrt, err := ioutil.ReadFile(secret.TLS.CertFile)
if err != nil {
return nil, err
}
tlsKey, err := ioutil.ReadFile(secret.TLS.KeyFile)
if err != nil {
return nil, err
}
corev1secret.Type = corev1.SecretTypeTLS
corev1secret.Data[corev1.TLSCertKey] = []byte(tlsCrt)
corev1secret.Data[corev1.TLSPrivateKeyKey] = []byte(tlsKey)
case "env":
err = cutil.HandleFromEnvFileSource(corev1secret, secret.EnvSource)
case "file":
err = cutil.HandleFromFileSources(corev1secret, secret.FileSources)
case "literal":
err = cutil.HandleFromLiteralSources(corev1secret, secret.LiteralSources)
default:
err = fmt.Errorf("unknown type of secret: %v", secret.Type)
}
if secret.FileSources != nil {
if err := cutil.HandleFromFileSources(corev1secret, secret.FileSources); err != nil {
return nil, err
}
}
if secret.LiteralSources != nil {
if err := cutil.HandleFromLiteralSources(corev1secret, secret.LiteralSources); err != nil {
return nil, err
}
}
return corev1secret, nil
}
func makeTlsSecret(secret manifest.TLSSecret) (*corev1.Secret, error) {
corev1secret := &corev1.Secret{}
corev1secret.APIVersion = "v1"
corev1secret.Kind = "Secret"
corev1secret.Name = secret.Name
corev1secret.Type = corev1.SecretTypeTLS
corev1secret.Data = map[string][]byte{}
if err := validateTLS(secret.CertFile, secret.KeyFile); err != nil {
return nil, err
}
tlsCrt, err := ioutil.ReadFile(secret.CertFile)
if err != nil {
return nil, err
}
tlsKey, err := ioutil.ReadFile(secret.KeyFile)
if err != nil {
return nil, err
}
corev1secret.Data[corev1.TLSCertKey] = []byte(tlsCrt)
corev1secret.Data[corev1.TLSPrivateKeyKey] = []byte(tlsKey)
return corev1secret, err
}

View File

@ -212,9 +212,8 @@ func TestConstructConfigMap(t *testing.T) {
{
description: "construct config map from env",
input: manifest.ConfigMap{
Type: "env",
NamePrefix: "envConfigMap",
Generic: manifest.Generic{
Name: "envConfigMap",
DataSources: manifest.DataSources{
EnvSource: "../examples/simple/instances/exampleinstance/configmap/app.env",
},
},
@ -223,9 +222,8 @@ func TestConstructConfigMap(t *testing.T) {
{
description: "construct config map from file",
input: manifest.ConfigMap{
Type: "file",
NamePrefix: "fileConfigMap",
Generic: manifest.Generic{
Name: "fileConfigMap",
DataSources: manifest.DataSources{
FileSources: []string{"../examples/simple/instances/exampleinstance/configmap/app-init.ini"},
},
},
@ -234,9 +232,8 @@ func TestConstructConfigMap(t *testing.T) {
{
description: "construct config map from literal",
input: manifest.ConfigMap{
Type: "literal",
NamePrefix: "literalConfigMap",
Generic: manifest.Generic{
Name: "literalConfigMap",
DataSources: manifest.DataSources{
LiteralSources: []string{"a=x", "b=y"},
},
},
@ -246,6 +243,36 @@ func TestConstructConfigMap(t *testing.T) {
for _, tc := range testCases {
cm, err := makeConfigMap(tc.input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(*cm, *tc.expected) {
t.Fatalf("in testcase: %q updated:\n%#v\ndoesn't match expected:\n%#v\n", tc.description, *cm, tc.expected)
}
}
}
func TestConstructTLSSecret(t *testing.T) {
type testCase struct {
description string
input manifest.TLSSecret
expected *corev1.Secret
}
testCases := []testCase{
{
description: "construct secret from tls",
input: manifest.TLSSecret{
Name: "tlsSecret",
CertFile: "../examples/simple/instances/exampleinstance/secret/tls.cert",
KeyFile: "../examples/simple/instances/exampleinstance/secret/tls.key",
},
expected: makeTLSSecret("tlsSecret"),
},
}
for _, tc := range testCases {
cm, err := makeTlsSecret(tc.input)
if err != nil {
t.Fatalf("unepxected error: %v", err)
}
@ -255,32 +282,19 @@ func TestConstructConfigMap(t *testing.T) {
}
}
func TestConstructSecret(t *testing.T) {
func TestConstructGenericSecret(t *testing.T) {
type testCase struct {
description string
input manifest.Secret
input manifest.GenericSecret
expected *corev1.Secret
}
testCases := []testCase{
{
description: "construct secret from tls",
input: manifest.Secret{
Type: "tls",
NamePrefix: "tlsSecret",
TLS: &manifest.TLS{
CertFile: "../examples/simple/instances/exampleinstance/secret/tls.cert",
KeyFile: "../examples/simple/instances/exampleinstance/secret/tls.key",
},
},
expected: makeTLSSecret("tlsSecret"),
},
{
description: "construct secret from env",
input: manifest.Secret{
Type: "env",
NamePrefix: "envSecret",
Generic: manifest.Generic{
input: manifest.GenericSecret{
Name: "envSecret",
DataSources: manifest.DataSources{
EnvSource: "../examples/simple/instances/exampleinstance/configmap/app.env",
},
},
@ -288,10 +302,9 @@ func TestConstructSecret(t *testing.T) {
},
{
description: "construct secret from file",
input: manifest.Secret{
Type: "file",
NamePrefix: "fileSecret",
Generic: manifest.Generic{
input: manifest.GenericSecret{
Name: "fileSecret",
DataSources: manifest.DataSources{
FileSources: []string{"../examples/simple/instances/exampleinstance/configmap/app-init.ini"},
},
},
@ -299,10 +312,9 @@ func TestConstructSecret(t *testing.T) {
},
{
description: "construct secret from literal",
input: manifest.Secret{
Type: "literal",
NamePrefix: "literalSecret",
Generic: manifest.Generic{
input: manifest.GenericSecret{
Name: "literalSecret",
DataSources: manifest.DataSources{
LiteralSources: []string{"a=x", "b=y"},
},
},
@ -311,9 +323,9 @@ func TestConstructSecret(t *testing.T) {
}
for _, tc := range testCases {
cm, err := makeSecret(tc.input)
cm, err := makeGenericSecret(tc.input)
if err != nil {
t.Fatalf("unepxected error: %v", err)
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(*cm, *tc.expected) {
t.Fatalf("in testcase: %q updated:\n%#v\ndoesn't match expected:\n%#v\n", tc.description, *cm, tc.expected)

View File

@ -22,19 +22,15 @@ patches:
- deployment/deployment.yaml
#There could also be configmaps in Base, which would make these overlays
configmaps:
- type: env
namePrefix: app-env
- name: app-env
env: configmap/app.env
- type: file
namePrefix: app-config
- name: app-config
files:
- configmap/app-init.ini
#There could be secrets in Base, if just using a fork/rebase workflow
secrets:
- type: tls
namePrefix: app-tls
tls:
certFile: secret/tls.cert
keyFile: secret/tls.key
tlsSecrets:
- name: app-tls
certFile: secret/tls.cert
keyFile: secret/tls.key
recursive: false
prune: true # Id make this the default

View File

@ -86,8 +86,8 @@ func populateConfigMapAndSecretMap(manifest *manifest.Manifest, m map[gvkn.Group
}
}
for _, secret := range manifest.Secrets {
unstructuredSecret, nameWithHash, err := cutil.MakeSecretAndGenerateName(secret)
for _, secret := range manifest.GenericSecrets {
unstructuredSecret, nameWithHash, err := cutil.MakeGenericSecretAndGenerateName(secret)
if err != nil {
return err
}
@ -96,6 +96,18 @@ func populateConfigMapAndSecretMap(manifest *manifest.Manifest, m map[gvkn.Group
return err
}
}
for _, secret := range manifest.TLSSecrets {
unstructuredSecret, nameWithHash, err := cutil.MakeTLSSecretAndGenerateName(secret)
if err != nil {
return err
}
err = populateMap(m, unstructuredSecret, nameWithHash)
if err != nil {
return err
}
}
return nil
}

View File

@ -64,6 +64,44 @@ func makeUnstructuredEnvSecret(name string) *unstructured.Unstructured {
}
}
func makeUnstructuredTLSSecret(name string) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "Secret",
"metadata": map[string]interface{}{
"name": name,
"creationTimestamp": nil,
},
"type": string(corev1.SecretTypeTLS),
"data": map[string]interface{}{
"tls.key": base64.StdEncoding.EncodeToString([]byte(`-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBANLJhPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wo
k/4xIA+ui35/MmNartNuC+BdZ1tMuVCPFZcCAwEAAQJAEJ2N+zsR0Xn8/Q6twa4G
6OB1M1WO+k+ztnX/1SvNeWu8D6GImtupLTYgjZcHufykj09jiHmjHx8u8ZZB/o1N
MQIhAPW+eyZo7ay3lMz1V01WVjNKK9QSn1MJlb06h/LuYv9FAiEA25WPedKgVyCW
SmUwbPw8fnTcpqDWE3yTO3vKcebqMSsCIBF3UmVue8YU3jybC3NxuXq3wNm34R8T
xVLHwDXh/6NJAiEAl2oHGGLz64BuAfjKrqwz7qMYr9HCLIe/YsoWq/olzScCIQDi
D2lWusoe2/nEqfDVVWGWlyJ7yOmqaVm/iNUN9B2N2g==
-----END RSA PRIVATE KEY-----
`)),
"tls.crt": base64.StdEncoding.EncodeToString([]byte(`-----BEGIN CERTIFICATE-----
MIIB0zCCAX2gAwIBAgIJAI/M7BYjwB+uMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTIwOTEyMjE1MjAyWhcNMTUwOTEyMjE1MjAyWjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANLJ
hPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wok/4xIA+ui35/MmNa
rtNuC+BdZ1tMuVCPFZcCAwEAAaNQME4wHQYDVR0OBBYEFJvKs8RfJaXTH08W+SGv
zQyKn0H8MB8GA1UdIwQYMBaAFJvKs8RfJaXTH08W+SGvzQyKn0H8MAwGA1UdEwQF
MAMBAf8wDQYJKoZIhvcNAQEFBQADQQBJlffJHybjDGxRMqaRmDhX0+6v02TUKZsW
r5QuVbpQhH6u+0UgcW0jp9QwpxoPTLTWGXEWBBBurxFwiCBhkQ+V
-----END CERTIFICATE-----
`)),
},
},
}
}
func TestPopulateMap(t *testing.T) {
expectedMap := map[gvkn.GroupVersionKindName]*unstructured.Unstructured{
{
@ -80,6 +118,13 @@ func TestPopulateMap(t *testing.T) {
},
Name: "envSecret",
}: makeUnstructuredEnvSecret("newNameSecret"),
{
GVK: schema.GroupVersionKind{
Version: "v1",
Kind: "Secret",
},
Name: "tlsSecret",
}: makeUnstructuredTLSSecret("newNameTLSSecret"),
}
m := map[gvkn.GroupVersionKindName]*unstructured.Unstructured{}
@ -91,6 +136,10 @@ func TestPopulateMap(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = populateMap(m, makeUnstructuredTLSSecret("tlsSecret"), "newNameTLSSecret")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(m, expectedMap) {
t.Fatalf("%#v\ndoesn't match expected\n%#v\n", m, expectedMap)
@ -107,18 +156,16 @@ func TestPopulateMapOfConfigMapAndSecret(t *testing.T) {
manifest := &manifest.Manifest{
Configmaps: []manifest.ConfigMap{
{
Type: "env",
NamePrefix: "envConfigMap",
Generic: manifest.Generic{
Name: "envConfigMap",
DataSources: manifest.DataSources{
EnvSource: "examples/simple/instances/exampleinstance/configmap/app.env",
},
},
},
Secrets: []manifest.Secret{
GenericSecrets: []manifest.GenericSecret{
{
Type: "env",
NamePrefix: "envSecret",
Generic: manifest.Generic{
Name: "envSecret",
DataSources: manifest.DataSources{
EnvSource: "examples/simple/instances/exampleinstance/configmap/app.env",
},
},