Skip to content

Commit

Permalink
Merge pull request #24 from vshn/namespace-watch
Browse files Browse the repository at this point in the history
Add namespace watcher
  • Loading branch information
ccremer committed Oct 7, 2020
2 parents b0912fe + bb4f50d commit a8e7a68
Show file tree
Hide file tree
Showing 21 changed files with 698 additions and 190 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Generate artifacts
run: make crd
- uses: goreleaser/goreleaser-action@v2
with:
args: release --snapshot --skip-sign
Expand All @@ -43,5 +45,5 @@ jobs:
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Run integration tests
run: make integration_test
- name: Run tests
run: make integration_test e2e_test
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ jobs:
${{ runner.os }}-go-
- name: Login to Docker hub
run: docker login -u ${{ secrets.DOCKER_HUB_USER }} -p ${{ secrets.DOCKER_HUB_PASSWORD }}
- name: Generate artifacts
run: make crd
- name: Publish releases
uses: goreleaser/goreleaser-action@v2
with:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ testbin/
# Kubernetes Generated files - skip generated files, except for vendored files

!vendor/**/zz_generated.*
espejo-crd.yaml

# editor and IDE paraphernalia
.idea
Expand Down
2 changes: 2 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,5 @@ release:
github:
owner: vshn
name: espejo
extra_files:
- glob: ./espejo-crd.yaml
46 changes: 43 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ IMG ?= controller:latest
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
CRD_OPTIONS ?= "crd:trivialVersions=true"

TESTBIN_DIR ?= ./testbin/bin
CRD_FILE ?= espejo-crd.yaml

KIND_BIN ?= $(TESTBIN_DIR)/kind
KIND_VERSION ?= 0.9.0
KIND_KUBECONFIG ?= ./testbin/kind-kubeconfig

# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
GOBIN=$(shell go env GOPATH)/bin
Expand All @@ -30,13 +37,17 @@ all: build

# Run tests (see https://sdk.operatorframework.io/docs/building-operators/golang/references/envtest-setup)
ENVTEST_ASSETS_DIR=$(shell pwd)/testbin
integration_test: generate fmt vet manifests
mkdir -p ${ENVTEST_ASSETS_DIR}

$(TESTBIN_DIR):
mkdir -p $(TESTBIN_DIR)

integration_test: export ENVTEST_K8S_VERSION = 1.19.0
integration_test: generate fmt vet manifests $(TESTBIN_DIR)
test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/master/hack/setup-envtest.sh
source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out -ginkgo.v

.PHONY: dist
dist:
dist: generate fmt vet
goreleaser release --snapshot --rm-dist --skip-sign

.PHONY: build
Expand Down Expand Up @@ -65,6 +76,10 @@ deploy: manifests kustomize
manifests: controller-gen
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases

# Generate CRD to file
crd: manifests kustomize
$(KUSTOMIZE) build config/crd > $(CRD_FILE)

# Run go fmt against code
fmt:
go fmt ./...
Expand Down Expand Up @@ -120,3 +135,28 @@ bundle: manifests
cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG)
$(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS)
operator-sdk bundle validate ./bundle

# Export kubeconfig for make's child process but not for user
e2e_test: export KUBECONFIG = $(KIND_KUBECONFIG)
e2e_test: setup_e2e_test build
@find e2e-test -type f -name *_test.sh | xargs -I % sh -c "echo --- TEST % && bash %"

setup_e2e_test: export KUBECONFIG = $(KIND_KUBECONFIG)
setup_e2e_test: $(KIND_BIN) manifests
@kubectl config use-context kind-espejo
@kubectl apply -k config/crd

run_kind: setup_e2e_test
go run ./main.go

$(KIND_BIN): export KUBECONFIG = $(KIND_KUBECONFIG)
$(KIND_BIN): $(TESTBIN_DIR)
curl -Lo $(KIND_BIN) "https://kind.sigs.k8s.io/dl/v$(KIND_VERSION)/kind-$$(uname)-amd64"
chmod +x $(KIND_BIN)
$(KIND_BIN) create cluster --name espejo
kubectl cluster-info

clean: export KUBECONFIG = $(KIND_KUBECONFIG)
clean:
$(KIND_BIN) delete cluster --name espejo || true
rm -r testbin/ dist/ bin/ cover.out || true
53 changes: 31 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,47 @@
# [Espejo](https://es.wikipedia.org/wiki/Espejo) (object-syncer)

[![Build](https://img.shields.io/github/workflow/status/vshn/espejo/Build)][build]
![Go version](https://img.shields.io/github/go-mod/go-version/vshn/espejo)
![Kubernetes version](https://img.shields.io/badge/k8s-v1.18-blue)
[![Version](https://img.shields.io/github/v/release/vshn/espejo)][releases]
[![GitHub downloads](https://img.shields.io/github/downloads/vshn/espejo/total)][releases]
[![Docker image](https://img.shields.io/docker/pulls/vshn/espejo)][dockerhub]
[![License](https://img.shields.io/github/license/vshn/espejo)][license]

The espejo tool (which means 'mirror' in Spanish) syncs objects from a SyncConfig CRD to multiple namespaces. The idea is to replace OpenShift's [project templates](https://docs.openshift.com/container-platform/3.11/admin_guide/managing_projects.html#modifying-the-template-for-new-projects) with a more flexible and robust solution.

## CustomResourceDefinitions
The operator introduces a CRD called `SyncConfig` to configure the objects which should be synced (see [example](deploy/crds/sync_v1alpha1_syncconfig_cr.yaml)).
[This `SyncConfig`](deploy/crds/sync_v1alpha1_syncconfig_cr.yaml) will create a `Service`, `Endpoints` and `NetworkPolicy` object in all namepsaces which mach the [label selector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.15/#labelselector-v1-meta) OR one of the name selectors.

The operator introduces a CRD called `SyncConfig` to configure the objects which should be synced.
[This `SyncConfig`](example/syncconfig.yaml) will create a `Service`, `Endpoints` and `NetworkPolicy` object in all namepsaces which mach the [label selector](https://v1-18.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#labelselector-v1-meta) OR one of the name selectors.
To ensure objects are deleted, set the `prune` parameter to `true` (default is `false`)

### Parameters
The following parameters can be used inside object definitions:

Strings within object definitions can be replaced with dynamic values with parameters. The following parameters can be used:

| Parameter Name | Description |
|------------------------------|------------------------------|
| `${PROJECT_NAME}` | Name of the target namespace |
| `${PROJECT_DISPLAYNAME}` | Not implemented |
| `${PROJECT_DESCRIPTION}` | Not implemented |
| `${PROJECT_ADMIN_USER}` | Not implemented |
| `${PROJECT_REQUESTING_USER}` | Not implemented |

The parameter replacement is implemented using the [OpenShift templates pkg](https://github.com/openshift/origin/tree/release-3.11/pkg/template/templateprocessing) and therefore follows the same [rules](https://docs.openshift.com/container-platform/3.11/dev_guide/templates.html#writing-parameters).
## Development

## Installation
The Operator is implemented with the [Operator SDK](https://github.com/operator-framework/operator-sdk) ([Installation](https://sdk.operatorframework.io/docs/installation/install-operator-sdk/)).

### Build

## Development
The tool is implemented with the [Operator SDK](https://github.com/operator-framework/operator-sdk).

1. [Install the Operator SDK](https://github.com/operator-framework/operator-sdk/blob/master/doc/user-guide.md#install-the-operator-sdk-cli)
2. Install dependencies
```bash
dep ensure
```
3. Run the operator
```bash
cd cmd/manager
go run main.go
```
`make build` places the binary in `bin/espejo`. Go is required.

### Integration tests

`make integration_test` runs unit test cases with a K8s test environment.

### End-to-End tests

`make e2e_test` will run [Kubernetes in Docker](https://kind.sigs.k8s.io/) to simulate a real-world K8s cluster so we can install and run the operator on it. Docker and Kubectl are required.


[build]: https://github.com/vshn/espejo/actions?query=workflow%3ABuild
[releases]: https://github.com/vshn/espejo/releases
[license]: https://github.com/vshn/espejo/blob/master/LICENSE
[dockerhub]: https://hub.docker.com/r/vshn/espejo
139 changes: 81 additions & 58 deletions api/v1alpha1/syncconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ http://www.apache.org/licenses/LICENSE-2.0
package v1alpha1

import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
Expand All @@ -14,65 +15,87 @@ import (
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.

// SyncConfigSpec defines the desired state of SyncConfig
type SyncConfigSpec struct {
// ForceRecreate defines if objects should be deleted and recreated if updates fails
ForceRecreate bool `json:"forceRecreate,omitempty"`
// NamespaceSelector defines which namespaces should be targeted
NamespaceSelector *NamespaceSelector `json:"namespaceSelector,omitempty"`
// SyncItems lists items to be synced to targeted namespaces
SyncItems []unstructured.Unstructured `json:"syncItems,omitempty"`
// DeleteItems lists items to be deleted from targeted namespaces
DeleteItems []DeleteMeta `json:"deleteItems,omitempty"`
}

// DeleteMeta defines an object by name, kind and version
type DeleteMeta struct {
// Name of the item to be deleted
Name string `json:"name,omitempty"`
// Kind of the item to be deleted
Kind string `json:"kind,omitempty"`
// APIVersion of the item to be deleted
APIVersion string `json:"apiVersion,omitempty"`
}

// NamespaceSelector provides a way to specify targeted namespaces
type NamespaceSelector struct {
// LabelSelector of namespaces to be targeted
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
// MatchNames lists namespace names to be targeted
MatchNames []string `json:"matchNames,omitempty"`
}

// SyncConfigStatus defines the observed state of SyncConfig
type SyncConfigStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file

// ReconcileError contains any error messages during synchronization, if any
ReconcileError string `json:"reconcileerror,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status

// SyncConfig is the Schema for the syncconfigs API
type SyncConfig struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec SyncConfigSpec `json:"spec,omitempty"`
Status SyncConfigStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true
type (
SyncConfigSpec struct {
// ForceRecreate defines if objects should be deleted and recreated if updates fails
ForceRecreate bool `json:"forceRecreate,omitempty"`
// NamespaceSelector defines which namespaces should be targeted
NamespaceSelector *NamespaceSelector `json:"namespaceSelector,omitempty"`
// SyncItems lists items to be synced to targeted namespaces
SyncItems []unstructured.Unstructured `json:"syncItems,omitempty"`
// DeleteItems lists items to be deleted from targeted namespaces
DeleteItems []DeleteMeta `json:"deleteItems,omitempty"`
}

// DeleteMeta defines an object by name, kind and version
DeleteMeta struct {
// Name of the item to be deleted
Name string `json:"name,omitempty"`
// Kind of the item to be deleted
Kind string `json:"kind,omitempty"`
// APIVersion of the item to be deleted
APIVersion string `json:"apiVersion,omitempty"`
}

// NamespaceSelector provides a way to specify targeted namespaces
NamespaceSelector struct {
// LabelSelector of namespaces to be targeted
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
// MatchNames lists namespace names to be targeted
MatchNames []string `json:"matchNames,omitempty"`
}

// SyncConfigStatus defines the observed state of SyncConfig
SyncConfigStatus struct {
// Conditions contain the states of the SyncConfig
Conditions []SyncConfigCondition `json:"conditions,omitempty" patchStrategy:"merge"`
// SynchronizedItemCount holds the number of created or updated objects in the targeted namespaces.
SynchronizedItemCount int64 `json:"synchronizedItemCount"`
// DeletedItemCount holds the number of deleted objects from targeted namespaces. Inexisting items do not get counted.
DeletedItemCount int64 `json:"deletedItemCount"`
// FailedItemCount holds the number of objects that could not be created, updated or deleted. Inexisting items do not get counted.
FailedItemCount int64 `json:"failedItemCount"`
}

SyncConfigCondition struct {
Type SyncConfigConditionType `json:"type"`
Status v1.ConditionStatus `json:"status"`
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
Reason string `json:"reason,omitempty"`
Message string `json:"message,omitempty"`
}
SyncConfigConditionType string

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status

// SyncConfig is the Schema for the syncconfigs API
SyncConfig struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec SyncConfigSpec `json:"spec,omitempty"`
Status SyncConfigStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// SyncConfigList contains a list of SyncConfig
SyncConfigList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []SyncConfig `json:"items"`
Prune bool `json:"prune,omitempty"`
}
)

// SyncConfigList contains a list of SyncConfig
type SyncConfigList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []SyncConfig `json:"items"`
Prune bool `json:"prune,omitempty"`
}
const (
// SyncConfigReady tracks if the SyncConfig has been successfully reconciled.
SyncConfigReady SyncConfigConditionType = "Ready"
// SyncConfigErrored is given when no objects could be synced or deleted and the failed object count is > 0 or
// any other reconciliation error.
SyncConfigErrored SyncConfigConditionType = "Errored"
)

func init() {
SchemeBuilder.Register(&SyncConfig{}, &SyncConfigList{})
Expand Down
25 changes: 24 additions & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a8e7a68

Please sign in to comment.