Skip to content

Commit

Permalink
Configure via a CRD instead of a yaml file
Browse files Browse the repository at this point in the history
  • Loading branch information
wr0ngway committed Apr 28, 2021
1 parent 822d7e1 commit 50ffb44
Show file tree
Hide file tree
Showing 17 changed files with 413 additions and 251 deletions.
1 change: 0 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ gemspec

gem "rake", "~> 12.0"
gem "rspec", "~> 3.0"
gem "test_construct"
gem "vcr"
gem "webmock"
gem "coveralls", ">= 0.8.23"
Expand Down
2 changes: 0 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ GEM
sync (0.5.0)
term-ansicolor (1.7.1)
tins (~> 1.0)
test_construct (2.0.2)
thor (1.0.1)
tins (1.26.0)
sync
Expand All @@ -140,7 +139,6 @@ DEPENDENCIES
rake (~> 12.0)
rspec (~> 3.0)
simplecov
test_construct
vcr
webmock

Expand Down
104 changes: 86 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,47 +103,115 @@ updated automatically in a running pod.

## Additional configuration

The kubetruth ConfigMap contains a [yaml file for additional config](helm/kubetruth/templates/configmap.yaml)
Kubetruth uses a CustomResourceDefinition called
[ProjectMapping(.kubetruth.cloudtruth.com)](helm/kubetruth/crds/projectmapping.yaml)
for additional configuration. The ProjectMapping CRD has two types identified
by the `scope` property, the `root` scope and the `override` scope. The `root`
scope is required, and there can be only one. It sets up the global behavior
for mapping the CloudTruth projects to kubernetes resources. You can edit it in
the standard ways, e.g. `kubectl edit projectmapping kubetruth-root`. The
`override` scope allows you to override the root scope's behavior by matching
its `project_selector` pattern against the CloudTruth project names already
selected by the root `project_selector`.

### Example Config

To create the kubernetes Resources in namespaces named after each Project:
```yaml
namespace_template: %{project}
The `projectmapping` resource has a shortname of `pm` for convenience when using kubectl.

To create kubernetes Resources in namespaces named after each Project:
```
kubectl patch pm kubetruth-root --type json --patch '[{"op": "replace", "path": "/spec/namespace_template", "value": "%{project}"}]'
```

To include the parameters from a Project named `Base` into all other projects, without creating Resources for `Base` itself:
```yaml
included_projects:
- Base
project_overrides:
- project_selector: Base
```
kubectl patch pm kubetruth-root --type json --patch '[{"op": "replace", "path": "/spec/included_projects", "value": ["Base"]}]'
kubectl apply -f - <<EOF
apiVersion: kubetruth.cloudtruth.com/v1
kind: ProjectMapping
metadata:
name: exclude-base
spec:
scope: override
project_selector: "^Base$"
skip: true
EOF
```

To override the naming of kubernetes Resources on a per-Project basis:
```yaml
project_overrides:
- project_selector: funkyProject
```
kubectl apply -f - <<EOF
apiVersion: kubetruth.cloudtruth.com/v1
kind: ProjectMapping
metadata:
name: funkyproject-special-naming
spec:
scope: override
project_selector: funkyProject
configmap_name_template: notSoFunkyConfigMap
secret_name_template: notSoFunkySecret
namespace_template: notSoFunkyNsmespace
EOF
```

To limit the Projects processed to those whose names start with `service`, except for `serviceOddball`:
```yaml
project_selector: ^service
project_overrides:
- project_selector: serviceOddball
skip: true
```

kubectl patch pm kubetruth-root --type json --patch '[{"op": "replace", "path": "/spec/project_selector", "value": "^service"}]'
kubectl apply -f - <<EOF
apiVersion: kubetruth.cloudtruth.com/v1
kind: ProjectMapping
metadata:
name: funkyproject-special-naming
spec:
scope: override
project_selector: serviceOddball
skip: true
EOF
```

To see the ProjectMappings that have been setup
```
$ kubectl get pm
NAME SCOPE PROJECT AGE
exclude-base override ^Base$ 7m6s
funkyproject-special-naming override serviceOddball 13s
kubetruth-root root ^service 27m

$ kubectl describe pm kubetruth-root
Name: kubetruth-root
Namespace: default
Labels: ...
Annotations: ...
API Version: kubetruth.cloudtruth.com/v1
Kind: ProjectMapping
Metadata:
...
Spec:
configmap_name_template: %{project}
included_projects:
key_filter:
key_selector:
key_template: %{key}
namespace_template:
project_selector:
Scope: root
secret_name_template: %{project}
Skip: false
skip_secrets: false
Events: <none>
```
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
To install and run via helm in a local cluster:
```
# If using minikube, ensure that docker builds the image into the minikube container
# with the command:
# eval $(minikube docker-env)
#
docker build -t kubetruth . && helm install \
--set image.repository=kubetruth --set image.pullPolicy=Never --set image.tag=latest \
--set appSettings.debug=true --set appSettings.apiKey=$CT_API_KEY --set appSettings.environment=development \
Expand Down
75 changes: 75 additions & 0 deletions helm/kubetruth/crds/projectmapping.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: projectmappings.kubetruth.cloudtruth.com
spec:
group: kubetruth.cloudtruth.com
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
scope:
type: string
description: Root or override scope, there can only be one root scope
enum: ["root", "override"]
default: "override"
project_selector:
type: string
description: A regexp to limit the projects acted against (client-side). Supplies any named matches for template evaluation
key_selector:
type: string
description: A regexp to limit the keys acted against (client-side). Supplies any named matches for template evaluation
key_filter:
type: string
description: Limits the keys fetched to contain the given substring (server-side, api search param)
configmap_name_template:
type: string
description: The template to use in generating ConfigMap names
secret_name_template:
type: string
description: The template to use in generating Secret names
namespace_template:
type: string
description: The template to use in generating namespace names
key_template:
type: string
description: The template to use in generating key names
skip:
type: boolean
description: Skips the generation of resources for the selected projects. Useful for excluding projects that should only be included into others.
skip_secrets:
type: boolean
description: Prevent transfer of secrets to kubernetes Secrets
included_projects:
type: array
items:
type: string
description: Include the parameters from other projects into the selected ones
required:
- scope
additionalPrinterColumns:
- name: Scope
type: string
description: The scope of the project mapping
jsonPath: .spec.scope
- name: Project
type: string
description: The selector used to target configuration by project
jsonPath: .spec.project_selector
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
scope: Namespaced
names:
plural: projectmappings
singular: projectmapping
kind: ProjectMapping
shortNames:
- pm
49 changes: 0 additions & 49 deletions helm/kubetruth/templates/configmap.yaml

This file was deleted.

3 changes: 3 additions & 0 deletions helm/kubetruth/templates/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ metadata:
namespace: {{ .Release.Namespace }}
name: {{ template "kubetruth.fullname" . }}
rules:
- apiGroups: ["kubetruth.cloudtruth.com"]
resources: ["projectmappings"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "create", "update", "delete"]
Expand Down
19 changes: 19 additions & 0 deletions helm/kubetruth/templates/rootprojectmapping.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
apiVersion: kubetruth.cloudtruth.com/v1
kind: ProjectMapping
metadata:
name: {{ include "kubetruth.fullname" . }}-root
namespace: {{ .Release.Namespace }}
labels:
{{- include "kubetruth.labels" . | nindent 4 }}
spec:
scope: "root"
project_selector: ""
key_selector: ""
key_filter: ""
configmap_name_template: "%{project}"
secret_name_template: "%{project}"
namespace_template: ""
key_template: "%{key}"
skip: false
skip_secrets: false
included_projects: []
6 changes: 1 addition & 5 deletions lib/kubetruth/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ class CLI < Clamp::Command
environment_variable: 'CT_API_KEY',
required: true

option ["-f", "--config-file"],
'FILE', "The kubetruth.yml file",
default: "/etc/kubetruth/kubetruth.yaml"

option "--kube-namespace",
'NAMESPACE', "The kubernetes namespace. Defaults to runtime namespace when run in kube"

Expand Down Expand Up @@ -93,7 +89,7 @@ def execute
api_url: kube_url
}

etl = ETL.new(config_file: config_file, ct_context: ct_context, kube_context: kube_context)
etl = ETL.new(ct_context: ct_context, kube_context: kube_context)

while true

Expand Down
26 changes: 11 additions & 15 deletions lib/kubetruth/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class Config
include GemLogger::LoggerSupport

ProjectSpec = Struct.new(
:scope,
:project_selector,
:key_selector,
:key_filter,
Expand All @@ -20,6 +21,7 @@ class Config
)

DEFAULT_SPEC = {
scope: 'override',
project_selector: '',
key_selector: '',
key_filter: '',
Expand All @@ -32,8 +34,8 @@ class Config
included_projects: []
}.freeze

def initialize(config_file:)
@config_file = config_file
def initialize(project_mapping_crds)
@project_mapping_crds = project_mapping_crds
end

def convert_types(hash)
Expand All @@ -43,21 +45,15 @@ def convert_types(hash)
end
end

def stale?
@last_read != File.mtime(@config_file)
end

def load
@config ||= begin
begin
config = YAML.load(File.read(@config_file)) || {}
@last_read = File.mtime(@config_file)
rescue => e
logger.warn("Unable to load config file: #{@config_file}, using defaults")
config = {}
end
overrides = config.delete(:project_overrides) || {}
config = DEFAULT_SPEC.merge(config)
parts = @project_mapping_crds.group_by {|c| c[:scope] }
raise ArgumentError.new("Multiple root ProjectMappings") if parts["root"] && parts["root"].size > 1

root_mapping = parts["root"]&.first || {}
overrides = parts["override"] || []

config = DEFAULT_SPEC.merge(root_mapping)
@root_spec = ProjectSpec.new(**convert_types(config))
@override_specs = overrides.collect { |o| ProjectSpec.new(convert_types(config.merge(o))) }
config
Expand Down
Loading

0 comments on commit 50ffb44

Please sign in to comment.