Skip to content

Commit

Permalink
Support to generate kubebuilder project configuration from a kustomiz…
Browse files Browse the repository at this point in the history
…e function
  • Loading branch information
pwittrock committed Dec 4, 2020
1 parent 6b775b8 commit 0271b6c
Show file tree
Hide file tree
Showing 54 changed files with 4,491 additions and 6 deletions.
80 changes: 80 additions & 0 deletions config-gen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Config-gen

This is a prototype of using a kustomize function to generate configuration.

To test it:

```sh
go build -o ~/go/bin/config-gen ./config-gen
```

## Usage Options

### With PROJECT file

Add the config gen fields to your project file

```yaml
# PROEJCT
metadata:
name: project-name # name used to generated resource names and namespaces
spec:
image: pwittrock/simple # controller-manager image to run
...
```

```sh
# from a kubebuilder project
config-gen
```

### With config file

Create a config.yaml

```yaml
# config.yaml
apiVersion: kubebuilder.sigs.k8s.io
kind: APIConfiguration
metadata:
name: project-name
spec:
image: example/simple:latest
```

```sh
# from a kubebuilder project
config-gen config.yaml
```

### With patch overrides

```yaml
# config.yaml
apiVersion: kubebuilder.sigs.k8s.io
kind: APIConfiguration
metadata:
name: project-name
spec:
image: example/simple:latest
```

```yaml
# patch1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: controller-manager
namespace: project-name-system
spec:
...
```

```sh
# from a kubebuilder project
config-gen config.yaml patch1.yaml
```

### From kustomize

config-gen may be run as a Kustomize plugin using kustomize
159 changes: 159 additions & 0 deletions config-gen/apis/v1alpha1/cert-generation-filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"encoding/base64"
"fmt"

"github.com/cloudflare/cfssl/cli/genkey"
"github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/csr"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/selfsign"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)

var _ kio.Filter = &CertFilter{}

// CertFilter generates and injects certificates into webhook
type CertFilter struct {
*KubebuilderProject
}

// Filter implements kio.Filter
func (c CertFilter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
if !c.Spec.Development.GenerateCert {
return input, nil
}
if err := c.generateCert(); err != nil {
return nil, err
}

s := &framework.Selector{
Kinds: []string{
"ValidatingWebhookConfiguration",
"MutatingWebhookConfiguration",
},
}
matches, err := s.GetMatches(&framework.ResourceList{Items: input})
if err != nil {
return nil, err
}
for i := range matches {
wh := matches[i].Field("webhooks")
if wh.IsNilOrEmpty() {
continue
}
err := wh.Value.VisitElements(func(node *yaml.RNode) error {
err := node.PipeE(yaml.LookupCreate(yaml.ScalarNode, "clientConfig", "caBundle"),
yaml.FieldSetter{StringValue: c.Status.CertCA})
if err != nil {
return err
}
err = node.PipeE(yaml.LookupCreate(yaml.ScalarNode, "clientConfig", "service", "namespace"),
yaml.FieldSetter{StringValue: c.Spec.Namespace})
if err != nil {
return err
}

return nil
})
if err != nil {
return nil, err
}
}

s = &framework.Selector{
Filter: func(n *yaml.RNode) bool {
// Allow-list conversion webhooks
m, _ := n.GetMeta()
if m.Kind != "CustomResourceDefinition" {
return true
}
return c.Spec.ConversionWebhooks[m.Name]
},
}
matches, err = s.GetMatches(&framework.ResourceList{Items: input})
if err != nil {
return nil, err
}
for i := range matches {
err := matches[i].PipeE(yaml.LookupCreate(yaml.ScalarNode, "spec", "conversion", "strategy"),
yaml.FieldSetter{StringValue: "Webhook"})
if err != nil {
return nil, err
}
err = matches[i].PipeE(yaml.LookupCreate(yaml.ScalarNode, "spec", "conversion", "webhookClientConfig", "caBundle"),
yaml.FieldSetter{StringValue: c.Status.CertCA})
if err != nil {
return nil, err
}
err = matches[i].PipeE(yaml.LookupCreate(yaml.ScalarNode, "spec", "conversion", "webhookClientConfig", "service", "name"),
yaml.FieldSetter{StringValue: "webhook-service"})
if err != nil {
return nil, err
}
err = matches[i].PipeE(yaml.LookupCreate(yaml.ScalarNode, "spec", "conversion", "webhookClientConfig", "service", "namespace"),
yaml.FieldSetter{StringValue: c.Spec.Namespace})
if err != nil {
return nil, err
}

err = matches[i].PipeE(yaml.LookupCreate(yaml.ScalarNode, "spec", "conversion", "webhookClientConfig", "service", "path"),
yaml.FieldSetter{StringValue: "/convert"})
if err != nil {
return nil, err
}
}

return input, nil
}

func (c CertFilter) generateCert() error {
var err error
var req = csr.New()
req.Hosts = []string{
fmt.Sprintf("webhook-service.%s.svc", c.Spec.Namespace),
fmt.Sprintf("webhook-service.%s.svc.cluster.local", c.Spec.Namespace),
}
req.CN = "kb-dev-controller-manager"

var key, csrPEM []byte
g := &csr.Generator{Validator: genkey.Validator}
csrPEM, key, err = g.ProcessRequest(req)
if err != nil {
return err
}
priv, err := helpers.ParsePrivateKeyPEM(key)
if err != nil {
return err
}

profile := config.DefaultConfig()
profile.Expiry = c.Spec.Development.CertDuration
cert, err := selfsign.Sign(priv, csrPEM, profile)
if err != nil {
return err
}

c.Status.CertCA = base64.StdEncoding.EncodeToString(cert)
c.Status.CertKey = base64.StdEncoding.EncodeToString(key)
return nil
}
22 changes: 22 additions & 0 deletions config-gen/apis/v1alpha1/cert-manager-patches.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package v1alpha1

import (
"github.com/markbates/pkger"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
)

// CertManagerPatchTemplate returns the PatchTemplate for cert-manager
func CertManagerPatchTemplate(kp *KubebuilderProject) framework.PT {
return framework.PT{
Dir: pkger.Dir("/config-gen/templates/patches/cert-manager"),
Selector: func() *framework.Selector {
return &framework.Selector{
Kinds: []string{
"CustomResourceDefinition",
"ValidatingWebhookConfiguration",
"MutatingWebhookConfiguration",
},
}
},
}
}
60 changes: 60 additions & 0 deletions config-gen/apis/v1alpha1/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"github.com/markbates/pkger"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/kio"
)

// NewCommand returns a new cobra command
func NewCommand() *cobra.Command {
kp := &KubebuilderProject{}

return framework.TemplateCommand{
API: kp,

MergeResources: true, // apply additional inputs as patches

// these are run before the templates
PreProcessFilters: []kio.Filter{
// run controller-gen libraries to generate configuration from code
ControllerGenFilter{KubebuilderProject: kp},
// inject generated certificates
CertFilter{KubebuilderProject: kp},
},

// generate resources
TemplatesFn: framework.TemplatesFromDir(pkger.Dir("/config-gen/templates/resources")),

// patch resources
PatchTemplatesFn: framework.PatchTemplatesFromDir(
CRDPatchTemplate(kp),
CertManagerPatchTemplate(kp),
ControllerManagerPatchTemplate(kp),
),

// perform final modifications
PostProcessFilters: []kio.Filter{
// sort the resources
ComponentFilter{KubebuilderProject: kp},
SortFilter{KubebuilderProject: kp},
},
}.GetCommand()
}
59 changes: 59 additions & 0 deletions config-gen/apis/v1alpha1/component-filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)

var _ kio.Filter = &ControllerGenFilter{}

// ComponentFilter inserts the component config read from disk into the ConfigMap
type ComponentFilter struct {
*KubebuilderProject
}

// Filter sets the component config in the configmap
func (cf ComponentFilter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
if !cf.Spec.ComponentConfigEnabled() {
return input, nil
}
s := &framework.Selector{
APIVersions: []string{"v1"},
Kinds: []string{"ConfigMap"},
Names: []string{"manager-config"},
Namespaces: []string{cf.Spec.Namespace},
}
matches, err := s.GetMatches(&framework.ResourceList{Items: input})
if err != nil {
return nil, err
}
for i := range matches {
m := matches[i]
value := yaml.NewStringRNode(cf.Status.ComponentConfigString)
value.YNode().Style = yaml.LiteralStyle
err := m.PipeE(
yaml.Lookup("data", "controller_manager_config.yaml"),
yaml.FieldSetter{OverrideStyle: true, Value: value})
if err != nil {
return nil, err
}
}
return input, nil
}
Loading

0 comments on commit 0271b6c

Please sign in to comment.