Skip to content

Commit

Permalink
Implement basic entrypoints (#23)
Browse files Browse the repository at this point in the history
Adds the `cmd` package and implements the controller/reconciler
entrypoints.

---------

Co-authored-by: Jordan Olshevski <[email protected]>
  • Loading branch information
jveski and Jordan Olshevski authored Dec 28, 2023
1 parent 993a14b commit 8f8e359
Show file tree
Hide file tree
Showing 17 changed files with 280 additions and 15 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

Work in progress.

## Development Environment

```bash
# Assumes kubectl is configured for your dev cluster (local or otherwise), and can pull images from $REGISTRY
export REGISTRY="your registry"
./dev/build.sh
```

## Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a
Expand Down
13 changes: 13 additions & 0 deletions cmd/eno-controller/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM docker.io/golang:1.21 AS builder
WORKDIR /app

ADD go.mod .
ADD go.sum .
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 go build ./cmd/eno-controller

FROM scratch
COPY --from=builder /app/eno-controller /eno-controller
ENTRYPOINT ["/eno-controller"]
75 changes: 75 additions & 0 deletions cmd/eno-controller/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package main

import (
"flag"
"fmt"
"os"
"time"

"github.com/go-logr/zapr"
"go.uber.org/zap"
ctrl "sigs.k8s.io/controller-runtime"

"github.com/Azure/eno/internal/controllers/synthesis"
"github.com/Azure/eno/internal/manager"
)

// TODO: Expose leader election options

func main() {
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "error: %s\n", err)
os.Exit(1)
}
}

func run() error {
ctx := ctrl.SetupSignalHandler()
var (
rolloutCooldown time.Duration
synconf = &synthesis.Config{}
)
flag.DurationVar(&synconf.Timeout, "synthesis-timeout", time.Minute, "Maximum lifespan of synthesizer pods")
flag.DurationVar(&rolloutCooldown, "rollout-cooldown", time.Second*30, "Minimum period of time between each ensuing composition update after a synthesizer is updated")
flag.Parse()

zl, err := zap.NewProduction()
if err != nil {
return err
}
logger := zapr.NewLogger(zl)

mgr, err := manager.New(logger, &manager.Options{
Rest: ctrl.GetConfigOrDie(),
})
if err != nil {
return fmt.Errorf("constructing manager: %w", err)
}

synconn, err := synthesis.NewSynthesizerConnection(mgr)
if err != nil {
return fmt.Errorf("constructing synthesizer connection: %w", err)
}

err = synthesis.NewExecController(mgr, synconn)
if err != nil {
return fmt.Errorf("constructing execution controller: %w", err)
}

err = synthesis.NewRolloutController(mgr, rolloutCooldown)
if err != nil {
return fmt.Errorf("constructing rollout controller: %w", err)
}

err = synthesis.NewStatusController(mgr)
if err != nil {
return fmt.Errorf("constructing status controller: %w", err)
}

err = synthesis.NewPodLifecycleController(mgr, synconf)
if err != nil {
return fmt.Errorf("constructing pod lifecycle controller: %w", err)
}

return mgr.Start(ctx)
}
13 changes: 13 additions & 0 deletions cmd/eno-reconciler/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM docker.io/golang:1.21 AS builder
WORKDIR /app

ADD go.mod .
ADD go.sum .
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 go build ./cmd/eno-reconciler

FROM scratch
COPY --from=builder /app/eno-reconciler /eno-reconciler
ENTRYPOINT ["/eno-reconciler"]
62 changes: 62 additions & 0 deletions cmd/eno-reconciler/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package main

import (
"flag"
"fmt"
"os"
"time"

"github.com/go-logr/zapr"
"go.uber.org/zap"
ctrl "sigs.k8s.io/controller-runtime"

"github.com/Azure/eno/internal/controllers/reconciliation"
"github.com/Azure/eno/internal/manager"
"github.com/Azure/eno/internal/reconstitution"
)

func main() {
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "error: %s\n", err)
os.Exit(1)
}
}

// TODO: Label filters, etc.

func run() error {
ctx := ctrl.SetupSignalHandler()
var (
rediscoverWhenNotFound bool
writeBatchInterval time.Duration
discoveryMaxRPS float32
)
flag.BoolVar(&rediscoverWhenNotFound, "rediscover-when-not-found", true, "Invalidate discovery cache when any type is not found in the openapi spec. Set this to false on <= k8s 1.14")
flag.DurationVar(&writeBatchInterval, "write-batch-interval", time.Second*5, "The max throughput of composition status updates")
flag.Parse()

zl, err := zap.NewProduction()
if err != nil {
return err
}
logger := zapr.NewLogger(zl)

mgr, err := manager.New(logger, &manager.Options{
Rest: ctrl.GetConfigOrDie(),
})
if err != nil {
return fmt.Errorf("constructing manager: %w", err)
}

recmgr, err := reconstitution.New(mgr, writeBatchInterval)
if err != nil {
return fmt.Errorf("constructing reconstitution manager: %w", err)
}

err = reconciliation.New(recmgr, mgr.GetConfig(), discoveryMaxRPS, rediscoverWhenNotFound)
if err != nil {
return fmt.Errorf("constructing reconciliation controller: %w", err)
}

return mgr.Start(ctx)
}
26 changes: 26 additions & 0 deletions dev/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/bash

set -e

if [[ -z "${REGISTRY}" ]]; then
echo "REGISTRY must be set" > /dev/stderr
exit 1
fi

export TAG="$(date +%s)"

function build() {
cmd=$(basename $1)
buildah build -t "$REGISTRY/$cmd:$TAG" -f "$f/Dockerfile"
buildah push "$REGISTRY/$cmd:$TAG"
}

# Build!
for f in cmd/*; do
build $f &
done
wait

# Deploy!
cat "$(dirname "$0")/deploy.yaml" | envsubst | kubectl apply -f -
echo "Success! You're running tag: $TAG"
41 changes: 41 additions & 0 deletions dev/deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: eno-controller
labels:
app: eno-controller
spec:
replicas: 1
selector:
matchLabels:
app: eno-controller
template:
metadata:
labels:
app: eno-controller
spec:
containers:
- name: eno-controller
image: $REGISTRY/eno-controller:$TAG

---

apiVersion: apps/v1
kind: Deployment
metadata:
name: eno-reconciler
labels:
app: eno-reconciler
spec:
replicas: 1
selector:
matchLabels:
app: eno-reconciler
template:
metadata:
labels:
app: eno-reconciler
spec:
containers:
- name: eno-reconciler
image: $REGISTRY/eno-reconciler:$TAG
26 changes: 26 additions & 0 deletions examples/simple.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
apiVersion: eno.azure.io/v1
kind: Synthesizer
metadata:
name: test-synth
spec:
image: docker.io/ubuntu:latest
command:
- /bin/bash
- -c
- |
echo '[{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": { "name": "test-cm", "namespace": "default" },
"data": { "foo": "bar" }
}]'
---

apiVersion: eno.azure.io/v1
kind: Composition
metadata:
name: test-comp
spec:
synthesizer:
name: test-synth
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ toolchain go1.21.5

require (
github.com/go-logr/logr v1.3.0
github.com/go-logr/zapr v1.3.0
github.com/google/gnostic-models v0.6.8
github.com/stretchr/testify v1.8.4
go.uber.org/zap v1.26.0
golang.org/x/time v0.3.0
k8s.io/api v0.29.0
k8s.io/apimachinery v0.29.0
Expand Down Expand Up @@ -52,7 +54,7 @@ require (
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
go.uber.org/zap v1.26.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/oauth2 v0.13.0 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=
github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=
Expand Down Expand Up @@ -114,6 +116,7 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down
3 changes: 3 additions & 0 deletions internal/controllers/reconciliation/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import (
"github.com/go-logr/logr"
)

// TODO: Block Composition deletion until resources have been cleaned up
// TODO: Clean up unused resource slices older than a duration

// TODO: Handle 400s better

type Controller struct {
Expand Down
4 changes: 1 addition & 3 deletions internal/controllers/reconciliation/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,7 @@ func TestCRUD(t *testing.T) {
require.NoError(t, synthesis.NewRolloutController(mgr.Manager, time.Millisecond))
require.NoError(t, synthesis.NewStatusController(mgr.Manager))
require.NoError(t, synthesis.NewPodLifecycleController(mgr.Manager, &synthesis.Config{
WrapperImage: "test-wrapper",
MaxRestarts: 2,
Timeout: time.Second * 5,
Timeout: time.Second * 5,
}))

// Simulate synthesis of our test composition into the resources specified by the test case
Expand Down
1 change: 1 addition & 0 deletions internal/controllers/synthesis/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ func (c *execController) synthesize(ctx context.Context, syn *apiv1.Synthesizer,
}

start := time.Now()
// TODO: Timeouts?
stdout, err := c.conn.Synthesize(ctx, syn, pod, inputsJson)
if err != nil {
return nil, err
Expand Down
4 changes: 1 addition & 3 deletions internal/controllers/synthesis/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ import (
)

var minimalTestConfig = &Config{
WrapperImage: "test-wrapper-image",
MaxRestarts: 3,
Timeout: time.Second * 6,
Timeout: time.Second * 6,
}

func TestControllerHappyPath(t *testing.T) {
Expand Down
5 changes: 1 addition & 4 deletions internal/controllers/synthesis/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ import (
)

type Config struct {
WrapperImage string
JobSA string
MaxRestarts int32
Timeout time.Duration
Timeout time.Duration
}

type podLifecycleController struct {
Expand Down
4 changes: 0 additions & 4 deletions internal/controllers/synthesis/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,6 @@ func newPod(cfg *Config, scheme *runtime.Scheme, comp *apiv1.Composition, syn *a
}},
}

if cfg.JobSA != "" {
pod.Spec.ServiceAccountName = cfg.JobSA
}

return pod
}

Expand Down
3 changes: 3 additions & 0 deletions internal/manager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
Expand Down Expand Up @@ -70,6 +71,8 @@ func New(logger logr.Logger, opts *Options) (ctrl.Manager, error) {
return nil, err
}

mgr.AddHealthzCheck("ping", healthz.Ping)

return mgr, nil
}

Expand Down

0 comments on commit 8f8e359

Please sign in to comment.