Skip to content

Commit

Permalink
Bootstrap operator
Browse files Browse the repository at this point in the history
This commit contains the first versions of the various CRDs and some
logic for the controllers.

Signed-off-by: Simon Beck <simon.beck@vshn.ch>
  • Loading branch information
Kidswiss committed Jan 22, 2020
1 parent 5d7ce0d commit 8eb782a
Show file tree
Hide file tree
Showing 41 changed files with 2,988 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
build/_output/*
17 changes: 17 additions & 0 deletions .vscode/launch.json
@@ -0,0 +1,17 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cmd/manager/main.go",
"env": {},
"args": []
}
]
}
15 changes: 15 additions & 0 deletions build/Dockerfile
@@ -0,0 +1,15 @@
FROM registry.access.redhat.com/ubi8/ubi-minimal:latest

ENV OPERATOR=/usr/local/bin/lieutenant-operator \
USER_UID=1001 \
USER_NAME=lieutenant-operator

# install operator binary
COPY build/_output/bin/lieutenant-operator ${OPERATOR}

COPY build/bin /usr/local/bin
RUN /usr/local/bin/user_setup

ENTRYPOINT ["/usr/local/bin/entrypoint"]

USER ${USER_UID}
12 changes: 12 additions & 0 deletions build/bin/entrypoint
@@ -0,0 +1,12 @@
#!/bin/sh -e

# This is documented here:
# https://docs.openshift.com/container-platform/3.11/creating_images/guidelines.html#openshift-specific-guidelines

if ! whoami &>/dev/null; then
if [ -w /etc/passwd ]; then
echo "${USER_NAME:-lieutenant-operator}:x:$(id -u):$(id -g):${USER_NAME:-lieutenant-operator} user:${HOME}:/sbin/nologin" >> /etc/passwd
fi
fi

exec ${OPERATOR} $@
13 changes: 13 additions & 0 deletions build/bin/user_setup
@@ -0,0 +1,13 @@
#!/bin/sh
set -x

# ensure $HOME exists and is accessible by group 0 (we don't know what the runtime UID will be)
mkdir -p ${HOME}
chown ${USER_UID}:0 ${HOME}
chmod ug+rwx ${HOME}

# runtime user will need to be able to self-insert in /etc/passwd
chmod g+rw /etc/passwd

# no need for this script to remain in the image after running
rm $0
188 changes: 188 additions & 0 deletions cmd/manager/main.go
@@ -0,0 +1,188 @@
package main

import (
"context"
"errors"
"flag"
"fmt"
"os"
"runtime"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/client-go/rest"

"github.com/projectsyn/lieutenant-operator/pkg/apis"
"github.com/projectsyn/lieutenant-operator/pkg/controller"
"github.com/projectsyn/lieutenant-operator/version"

"github.com/operator-framework/operator-sdk/pkg/k8sutil"
kubemetrics "github.com/operator-framework/operator-sdk/pkg/kube-metrics"
"github.com/operator-framework/operator-sdk/pkg/leader"
"github.com/operator-framework/operator-sdk/pkg/log/zap"
"github.com/operator-framework/operator-sdk/pkg/metrics"
sdkVersion "github.com/operator-framework/operator-sdk/version"
"github.com/spf13/pflag"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client/config"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
)

// Change below variables to serve metrics on different host or port.
var (
metricsHost = "0.0.0.0"
metricsPort int32 = 8383
operatorMetricsPort int32 = 8686
)
var log = logf.Log.WithName("cmd")

func printVersion() {
log.Info(fmt.Sprintf("Operator Version: %s", version.Version))
log.Info(fmt.Sprintf("Go Version: %s", runtime.Version()))
log.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH))
log.Info(fmt.Sprintf("Version of operator-sdk: %v", sdkVersion.Version))
}

func main() {
// Add the zap logger flag set to the CLI. The flag set must
// be added before calling pflag.Parse().
pflag.CommandLine.AddFlagSet(zap.FlagSet())

// Add flags registered by imported packages (e.g. glog and
// controller-runtime)
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)

pflag.Parse()

// Use a zap logr.Logger implementation. If none of the zap
// flags are configured (or if the zap flag set is not being
// used), this defaults to a production zap logger.
//
// The logger instantiated here can be changed to any logger
// implementing the logr.Logger interface. This logger will
// be propagated through the whole operator, generating
// uniform and structured logs.
logf.SetLogger(zap.Logger())

printVersion()

namespace, err := k8sutil.GetWatchNamespace()
if err != nil {
log.Info("Failed to get watch namespace, setting empty")
namespace = ""
}

// Get a config to talk to the apiserver
cfg, err := config.GetConfig()
if err != nil {
log.Error(err, "")
os.Exit(1)
}

ctx := context.TODO()
// Become the leader before proceeding
err = leader.Become(ctx, "lieutenant-operator-lock")
if err != nil {
log.Error(err, "")
os.Exit(1)
}

// Create a new Cmd to provide shared dependencies and start components
mgr, err := manager.New(cfg, manager.Options{
Namespace: namespace,
MetricsBindAddress: fmt.Sprintf("%s:%d", metricsHost, metricsPort),
})
if err != nil {
log.Error(err, "")
os.Exit(1)
}

log.Info("Registering Components.")

// Setup Scheme for all resources
if err := apis.AddToScheme(mgr.GetScheme()); err != nil {
log.Error(err, "")
os.Exit(1)
}

// Setup all Controllers
if err := controller.AddToManager(mgr); err != nil {
log.Error(err, "")
os.Exit(1)
}

// Add the Metrics Service
addMetrics(ctx, cfg, namespace)

log.Info("Starting the Cmd.")

// Start the Cmd
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
log.Error(err, "Manager exited non-zero")
os.Exit(1)
}
}

// addMetrics will create the Services and Service Monitors to allow the operator export the metrics by using
// the Prometheus operator
func addMetrics(ctx context.Context, cfg *rest.Config, namespace string) {
if err := serveCRMetrics(cfg); err != nil {
if errors.Is(err, k8sutil.ErrRunLocal) {
log.Info("Skipping CR metrics server creation; not running in a cluster.")
return
}
log.Info("Could not generate and serve custom resource metrics", "error", err.Error())
}

// Add to the below struct any other metrics ports you want to expose.
servicePorts := []v1.ServicePort{
{Port: metricsPort, Name: metrics.OperatorPortName, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: metricsPort}},
{Port: operatorMetricsPort, Name: metrics.CRPortName, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: operatorMetricsPort}},
}

// Create Service object to expose the metrics port(s).
service, err := metrics.CreateMetricsService(ctx, cfg, servicePorts)
if err != nil {
log.Info("Could not create metrics Service", "error", err.Error())
}

// CreateServiceMonitors will automatically create the prometheus-operator ServiceMonitor resources
// necessary to configure Prometheus to scrape metrics from this operator.
services := []*v1.Service{service}
_, err = metrics.CreateServiceMonitors(cfg, namespace, services)
if err != nil {
log.Info("Could not create ServiceMonitor object", "error", err.Error())
// If this operator is deployed to a cluster without the prometheus-operator running, it will return
// ErrServiceMonitorNotPresent, which can be used to safely skip ServiceMonitor creation.
if err == metrics.ErrServiceMonitorNotPresent {
log.Info("Install prometheus-operator in your cluster to create ServiceMonitor objects", "error", err.Error())
}
}
}

// serveCRMetrics gets the Operator/CustomResource GVKs and generates metrics based on those types.
// It serves those metrics on "http://metricsHost:operatorMetricsPort".
func serveCRMetrics(cfg *rest.Config) error {
// Below function returns filtered operator/CustomResource specific GVKs.
// For more control override the below GVK list with your own custom logic.
filteredGVK, err := k8sutil.GetGVKsFromAddToScheme(apis.AddToScheme)
if err != nil {
return err
}
// Get the namespace the operator is currently deployed in.
operatorNs, err := k8sutil.GetOperatorNamespace()
if err != nil {
return err
}
// To generate metrics in other namespaces, add the values below.
ns := []string{operatorNs}
// Generate and serve custom resource specific metrics.
err = kubemetrics.GenerateAndServeCRMetrics(cfg, ns, filteredGVK, metricsHost, operatorMetricsPort)
if err != nil {
return err
}
return nil
}
147 changes: 147 additions & 0 deletions deploy/crds/syn.tools_clusters_crd.yaml
@@ -0,0 +1,147 @@
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: clusters.syn.tools
spec:
group: syn.tools
names:
kind: Cluster
listKind: ClusterList
plural: clusters
singular: cluster
scope: Namespaced
subresources:
status: {}
validation:
openAPIV3Schema:
description: Cluster is the Schema for the clusters API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: ClusterSpec defines the desired state of Cluster
properties:
apiEndpointSecretRef:
description: APIEndpointSecretRef references the secret containing connection
information to Kubernetes API endpoint of the registered Kubernetes
cluster.
properties:
name:
description: Name is unique within a namespace to reference a secret
resource.
type: string
namespace:
description: Namespace defines the space within which the secret
name must be unique.
type: string
type: object
displayName:
description: DisplayName of cluster which could be different from metadata.name.
Allows cluster renaming should it be needed.
type: string
facts:
additionalProperties:
description: FactValue is the value for the facts map
type: string
description: Facts are key/value pairs for statically configured facts
type: object
gitRepoTemplate:
description: GitRepoTemplate template for managing the GitRepo object.
properties:
spec:
description: GitRepoSpec defines the desired state of GitRepo
properties:
apiSecretRef:
description: APISecretRef reference to secret containing connection
information
properties:
name:
description: Name is unique within a namespace to reference
a secret resource.
type: string
namespace:
description: Namespace defines the space within which the
secret name must be unique.
type: string
type: object
deployKeys:
description: DeployKeys optional list of SSH deploy keys. If
not set, not deploy keys will be configured
items:
description: DeployKey defines an SSH key to be used for git
operations.
properties:
key:
type: string
type:
type: string
writeAccess:
type: boolean
type: object
type: array
path:
description: Path to Git repository
type: string
repoName:
description: RepoName ame of Git repository
type: string
tenantRef:
description: TenantRef references the tenant this repo belongs
to
properties:
name:
type: string
namespace:
type: string
type: object
type: object
type: object
gitRepoURL:
description: GitRepoURL git repository storing the cluster configuration
catalog. If this is set, no gitRepoTemplate is needed.
type: string
tenantRef:
description: TenantRef reference to Tenant object the cluster belongs
to.
properties:
name:
type: string
namespace:
type: string
type: object
tokenLifeTime:
description: TokenLifetime set the token lifetime
type: string
type: object
status:
description: ClusterStatus defines the observed state of Cluster
properties:
bootstrapToken:
description: BootstrapTokenValid validity of the bootstrap token, set
by the Lieutenant API.
properties:
bootstrapTokenValid:
type: boolean
token:
type: string
validUntil:
format: date-time
type: string
type: object
type: object
type: object
version: v1alpha1
versions:
- name: v1alpha1
served: true
storage: true

0 comments on commit 8eb782a

Please sign in to comment.