Browse Source

import

tags/0.0.1
Vladimir Smagin 1 week ago
commit
6370eb87b0
100 changed files with 21393 additions and 0 deletions
  1. +77
    -0
      .gitignore
  2. +18
    -0
      Dockerfile
  3. +48
    -0
      Jenkinsfile
  4. +15
    -0
      build/Dockerfile
  5. +3
    -0
      build/bin/entrypoint
  6. +11
    -0
      build/bin/user_setup
  7. +211
    -0
      cmd/manager/main.go
  8. +7
    -0
      deploy/crds/blindage.org_v1alpha1_warmimage_cr.yaml
  9. +691
    -0
      deploy/crds/blindage.org_warmimages_crd.yaml
  10. +33
    -0
      deploy/operator.yaml
  11. +80
    -0
      deploy/role.yaml
  12. +11
    -0
      deploy/role_binding.yaml
  13. +4
    -0
      deploy/service_account.yaml
  14. +19
    -0
      go.mod
  15. +1210
    -0
      go.sum
  16. +10
    -0
      pkg/apis/addtoscheme_blindage_v1alpha1.go
  17. +13
    -0
      pkg/apis/apis.go
  18. +6
    -0
      pkg/apis/blindage/group.go
  19. +4
    -0
      pkg/apis/blindage/v1alpha1/doc.go
  20. +19
    -0
      pkg/apis/blindage/v1alpha1/register.go
  21. +48
    -0
      pkg/apis/blindage/v1alpha1/warmimage_types.go
  22. +165
    -0
      pkg/apis/blindage/v1alpha1/zz_generated.deepcopy.go
  23. +10
    -0
      pkg/controller/add_warmimage.go
  24. +18
    -0
      pkg/controller/controller.go
  25. +93
    -0
      pkg/controller/warmimage/manifests.go
  26. +31
    -0
      pkg/controller/warmimage/reconcile.go
  27. +40
    -0
      pkg/controller/warmimage/utils.go
  28. +138
    -0
      pkg/controller/warmimage/warmimage_controller.go
  29. +5
    -0
      tools.go
  30. +202
    -0
      vendor/cloud.google.com/go/LICENSE
  31. +12
    -0
      vendor/cloud.google.com/go/compute/metadata/.repo-metadata.json
  32. +526
    -0
      vendor/cloud.google.com/go/compute/metadata/metadata.go
  33. +191
    -0
      vendor/github.com/Azure/go-autorest/autorest/LICENSE
  34. +191
    -0
      vendor/github.com/Azure/go-autorest/autorest/adal/LICENSE
  35. +292
    -0
      vendor/github.com/Azure/go-autorest/autorest/adal/README.md
  36. +151
    -0
      vendor/github.com/Azure/go-autorest/autorest/adal/config.go
  37. +269
    -0
      vendor/github.com/Azure/go-autorest/autorest/adal/devicetoken.go
  38. +12
    -0
      vendor/github.com/Azure/go-autorest/autorest/adal/go.mod
  39. +23
    -0
      vendor/github.com/Azure/go-autorest/autorest/adal/go.sum
  40. +24
    -0
      vendor/github.com/Azure/go-autorest/autorest/adal/go_mod_tidy_hack.go
  41. +73
    -0
      vendor/github.com/Azure/go-autorest/autorest/adal/persist.go
  42. +95
    -0
      vendor/github.com/Azure/go-autorest/autorest/adal/sender.go
  43. +1130
    -0
      vendor/github.com/Azure/go-autorest/autorest/adal/token.go
  44. +45
    -0
      vendor/github.com/Azure/go-autorest/autorest/adal/version.go
  45. +336
    -0
      vendor/github.com/Azure/go-autorest/autorest/authorization.go
  46. +67
    -0
      vendor/github.com/Azure/go-autorest/autorest/authorization_sas.go
  47. +301
    -0
      vendor/github.com/Azure/go-autorest/autorest/authorization_storage.go
  48. +150
    -0
      vendor/github.com/Azure/go-autorest/autorest/autorest.go
  49. +924
    -0
      vendor/github.com/Azure/go-autorest/autorest/azure/async.go
  50. +335
    -0
      vendor/github.com/Azure/go-autorest/autorest/azure/azure.go
  51. +244
    -0
      vendor/github.com/Azure/go-autorest/autorest/azure/environments.go
  52. +245
    -0
      vendor/github.com/Azure/go-autorest/autorest/azure/metadata_environment.go
  53. +204
    -0
      vendor/github.com/Azure/go-autorest/autorest/azure/rp.go
  54. +300
    -0
      vendor/github.com/Azure/go-autorest/autorest/client.go
  55. +191
    -0
      vendor/github.com/Azure/go-autorest/autorest/date/LICENSE
  56. +96
    -0
      vendor/github.com/Azure/go-autorest/autorest/date/date.go
  57. +5
    -0
      vendor/github.com/Azure/go-autorest/autorest/date/go.mod
  58. +16
    -0
      vendor/github.com/Azure/go-autorest/autorest/date/go.sum
  59. +24
    -0
      vendor/github.com/Azure/go-autorest/autorest/date/go_mod_tidy_hack.go
  60. +103
    -0
      vendor/github.com/Azure/go-autorest/autorest/date/time.go
  61. +100
    -0
      vendor/github.com/Azure/go-autorest/autorest/date/timerfc1123.go
  62. +123
    -0
      vendor/github.com/Azure/go-autorest/autorest/date/unixtime.go
  63. +25
    -0
      vendor/github.com/Azure/go-autorest/autorest/date/utility.go
  64. +98
    -0
      vendor/github.com/Azure/go-autorest/autorest/error.go
  65. +11
    -0
      vendor/github.com/Azure/go-autorest/autorest/go.mod
  66. +18
    -0
      vendor/github.com/Azure/go-autorest/autorest/go.sum
  67. +550
    -0
      vendor/github.com/Azure/go-autorest/autorest/preparer.go
  68. +269
    -0
      vendor/github.com/Azure/go-autorest/autorest/responder.go
  69. +52
    -0
      vendor/github.com/Azure/go-autorest/autorest/retriablerequest.go
  70. +54
    -0
      vendor/github.com/Azure/go-autorest/autorest/retriablerequest_1.7.go
  71. +66
    -0
      vendor/github.com/Azure/go-autorest/autorest/retriablerequest_1.8.go
  72. +407
    -0
      vendor/github.com/Azure/go-autorest/autorest/sender.go
  73. +228
    -0
      vendor/github.com/Azure/go-autorest/autorest/utility.go
  74. +41
    -0
      vendor/github.com/Azure/go-autorest/autorest/version.go
  75. +191
    -0
      vendor/github.com/Azure/go-autorest/logger/LICENSE
  76. +3
    -0
      vendor/github.com/Azure/go-autorest/logger/go.mod
  77. +328
    -0
      vendor/github.com/Azure/go-autorest/logger/logger.go
  78. +191
    -0
      vendor/github.com/Azure/go-autorest/tracing/LICENSE
  79. +3
    -0
      vendor/github.com/Azure/go-autorest/tracing/go.mod
  80. +67
    -0
      vendor/github.com/Azure/go-autorest/tracing/tracing.go
  81. +20
    -0
      vendor/github.com/beorn7/perks/LICENSE
  82. +2388
    -0
      vendor/github.com/beorn7/perks/quantile/exampledata.txt
  83. +316
    -0
      vendor/github.com/beorn7/perks/quantile/stream.go
  84. +13
    -0
      vendor/github.com/certifi/gocertifi/.travis.yml
  85. +373
    -0
      vendor/github.com/certifi/gocertifi/LICENSE
  86. +74
    -0
      vendor/github.com/certifi/gocertifi/README.md
  87. +4663
    -0
      vendor/github.com/certifi/gocertifi/certifi.go
  88. +3
    -0
      vendor/github.com/certifi/gocertifi/go.mod
  89. +8
    -0
      vendor/github.com/cespare/xxhash/v2/.travis.yml
  90. +22
    -0
      vendor/github.com/cespare/xxhash/v2/LICENSE.txt
  91. +67
    -0
      vendor/github.com/cespare/xxhash/v2/README.md
  92. +3
    -0
      vendor/github.com/cespare/xxhash/v2/go.mod
  93. +0
    -0
      vendor/github.com/cespare/xxhash/v2/go.sum
  94. +236
    -0
      vendor/github.com/cespare/xxhash/v2/xxhash.go
  95. +13
    -0
      vendor/github.com/cespare/xxhash/v2/xxhash_amd64.go
  96. +215
    -0
      vendor/github.com/cespare/xxhash/v2/xxhash_amd64.s
  97. +76
    -0
      vendor/github.com/cespare/xxhash/v2/xxhash_other.go
  98. +15
    -0
      vendor/github.com/cespare/xxhash/v2/xxhash_safe.go
  99. +46
    -0
      vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go
  100. +202
    -0
      vendor/github.com/coreos/prometheus-operator/LICENSE

+ 77
- 0
.gitignore View File

@@ -0,0 +1,77 @@
# Temporary Build Files
build/_output
build/_test
# Created by https://www.gitignore.io/api/go,vim,emacs,visualstudiocode
### Emacs ###
# -*- mode: gitignore; -*-
*~
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
*.elc
auto-save-list
tramp
.\#*
# Org-mode
.org-id-locations
*_archive
# flymake-mode
*_flymake.*
# eshell files
/eshell/history
/eshell/lastdir
# elpa packages
/elpa/
# reftex files
*.rel
# AUCTeX auto folder
/auto/
# cask packages
.cask/
dist/
# Flycheck
flycheck_*.el
# server auth directory
/server/
# projectiles files
.projectile
projectile-bookmarks.eld
# directory configuration
.dir-locals.el
# saveplace
places
# url cache
url/cache/
# cedet
ede-projects.el
# smex
smex-items
# company-statistics
company-statistics-cache.el
# anaconda-mode
anaconda-mode/
### Go ###
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with 'go test -c'
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
### Vim ###
# swap
.sw[a-p]
.*.sw[a-p]
# session
Session.vim
# temporary
.netrwhist
# auto-generated tag files
tags
### VisualStudioCode ###
.vscode/*
.history
# End of https://www.gitignore.io/api/go,vim,emacs,visualstudiocode

+ 18
- 0
Dockerfile View File

@@ -0,0 +1,18 @@
FROM golang:1.14

RUN apt update
RUN apt install -y git mercurial

WORKDIR /app/operator
COPY . .

WORKDIR /app/operator/cmd/manager

RUN CGO_ENABLED=0 go build -mod=vendor
RUN go install -mod=vendor

FROM ubuntu:20.04

COPY --from=0 /go/bin/manager /usr/local/bin/warm-image-operator

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

+ 48
- 0
Jenkinsfile View File

@@ -0,0 +1,48 @@
node {
checkout scm

def branch = env.BRANCH_NAME
def build = env.BUILD_NUMBER
def image
def repository = "registry.blindage.org/warm-image-operator"
def image_name = "${repository}"
def commit_hash = checkout(scm).GIT_COMMIT
def version

stage("Read version from file") {
try {
version = readFile file: "VERSION"
echo "Detected version: ${version}"
image_name = "${repository}:${version}"
} catch (Exception e) {
error("Failed to read version from file")
}
}

stage("Build Image") {
try {
sh "docker build -t ${image_name} ."
image = docker.image(image_name)
} catch (Exception e) {
error("Failed to build image")
}
}

if (branch == 'master') {
stage("Push Image") {
try {

docker.withRegistry("", "dockerhub") {
image.push()
}

} catch(Exception e) {
echo "${e}"
error("Failed to push image")
} finally {
// cleanup
sh "docker rmi ${image_name} || true"
}
}
}
}

+ 15
- 0
build/Dockerfile View File

@@ -0,0 +1,15 @@
FROM registry.access.redhat.com/ubi8/ubi-minimal:latest

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

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

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

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

USER ${USER_UID}

+ 3
- 0
build/bin/entrypoint View File

@@ -0,0 +1,3 @@
#!/bin/sh -e

exec ${OPERATOR} $@

+ 11
- 0
build/bin/user_setup View File

@@ -0,0 +1,11 @@
#!/bin/sh
set -x

# ensure $HOME exists and is accessible by group 0 (we don't know what the runtime UID will be)
echo "${USER_NAME}:x:${USER_UID}:0:${USER_NAME} user:${HOME}:/sbin/nologin" >> /etc/passwd
mkdir -p "${HOME}"
chown "${USER_UID}:0" "${HOME}"
chmod ug+rwx "${HOME}"

# no need for this script to remain in the image after running
rm "$0"

+ 211
- 0
cmd/manager/main.go View File

@@ -0,0 +1,211 @@
package main

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

// 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"

"git.blindage.org/21h/warm-image-operator/pkg/apis"
"git.blindage.org/21h/warm-image-operator/pkg/controller"
"git.blindage.org/21h/warm-image-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/cache"
"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.Error(err, "Failed to get watch namespace")
os.Exit(1)
}

// 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, "warm-image-operator-lock")
if err != nil {
log.Error(err, "")
os.Exit(1)
}

// Set default manager options
options := manager.Options{
Namespace: namespace,
MetricsBindAddress: fmt.Sprintf("%s:%d", metricsHost, metricsPort),
}

// Add support for MultiNamespace set in WATCH_NAMESPACE (e.g ns1,ns2)
// Note that this is not intended to be used for excluding namespaces, this is better done via a Predicate
// Also note that you may face performance issues when using this with a high number of namespaces.
// More Info: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/cache#MultiNamespacedCacheBuilder
if strings.Contains(namespace, ",") {
options.Namespace = ""
options.NewCache = cache.MultiNamespacedCacheBuilder(strings.Split(namespace, ","))
}

// Create a new manager to provide shared dependencies and start components
mgr, err := manager.New(cfg, options)
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)

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) {
// Get the namespace the operator is currently deployed in.
operatorNs, err := k8sutil.GetOperatorNamespace()
if err != nil {
if errors.Is(err, k8sutil.ErrRunLocal) {
log.Info("Skipping CR metrics server creation; not running in a cluster.")
return
}
}

if err := serveCRMetrics(cfg, operatorNs); err != nil {
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}

// The ServiceMonitor is created in the same namespace where the operator is deployed
_, err = metrics.CreateServiceMonitors(cfg, operatorNs, 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, operatorNs string) error {
// The function below returns a list of filtered operator/CR specific GVKs. For more control, override the GVK list below
// with your own custom logic. Note that if you are adding third party API schemas, probably you will need to
// customize this implementation to avoid permissions issues.
filteredGVK, err := k8sutil.GetGVKsFromAddToScheme(apis.AddToScheme)
if err != nil {
return err
}

// The metrics will be generated from the namespaces which are returned here.
// NOTE that passing nil or an empty list of namespaces in GenerateAndServeCRMetrics will result in an error.
ns, err := kubemetrics.GetNamespacesForMetrics(operatorNs)
if err != nil {
return err
}

// Generate and serve custom resource specific metrics.
err = kubemetrics.GenerateAndServeCRMetrics(cfg, ns, filteredGVK, metricsHost, operatorMetricsPort)
if err != nil {
return err
}
return nil
}

+ 7
- 0
deploy/crds/blindage.org_v1alpha1_warmimage_cr.yaml View File

@@ -0,0 +1,7 @@
apiVersion: blindage.org/v1alpha1
kind: WarmImage
metadata:
name: example-warmimage
spec:
# Add fields here
size: 3

+ 691
- 0
deploy/crds/blindage.org_warmimages_crd.yaml View File

@@ -0,0 +1,691 @@
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: warmimages.blindage.org
spec:
group: blindage.org
names:
kind: WarmImage
listKind: WarmImageList
plural: warmimages
singular: warmimage
scope: Namespaced
subresources:
status: {}
validation:
openAPIV3Schema:
description: WarmImage is the Schema for the warmimages 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: WarmImageSpec defines the desired state of WarmImage
properties:
affinity:
description: Affinity is a group of affinity scheduling rules.
properties:
nodeAffinity:
description: Describes node affinity scheduling rules for the pod.
properties:
preferredDuringSchedulingIgnoredDuringExecution:
description: The scheduler will prefer to schedule pods to nodes
that satisfy the affinity expressions specified by this field,
but it may choose a node that violates one or more of the
expressions. The node that is most preferred is the one with
the greatest sum of weights, i.e. for each node that meets
all of the scheduling requirements (resource request, requiredDuringScheduling
affinity expressions, etc.), compute a sum by iterating through
the elements of this field and adding "weight" to the sum
if the node matches the corresponding matchExpressions; the
node(s) with the highest sum are the most preferred.
items:
description: An empty preferred scheduling term matches all
objects with implicit weight 0 (i.e. it's a no-op). A null
preferred scheduling term matches no objects (i.e. is also
a no-op).
properties:
preference:
description: A node selector term, associated with the
corresponding weight.
properties:
matchExpressions:
description: A list of node selector requirements
by node's labels.
items:
description: A node selector requirement is a selector
that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: The label key that the selector
applies to.
type: string
operator:
description: Represents a key's relationship
to a set of values. Valid operators are In,
NotIn, Exists, DoesNotExist. Gt, and Lt.
type: string
values:
description: An array of string values. If the
operator is In or NotIn, the values array
must be non-empty. If the operator is Exists
or DoesNotExist, the values array must be
empty. If the operator is Gt or Lt, the values
array must have a single element, which will
be interpreted as an integer. This array is
replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchFields:
description: A list of node selector requirements
by node's fields.
items:
description: A node selector requirement is a selector
that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: The label key that the selector
applies to.
type: string
operator:
description: Represents a key's relationship
to a set of values. Valid operators are In,
NotIn, Exists, DoesNotExist. Gt, and Lt.
type: string
values:
description: An array of string values. If the
operator is In or NotIn, the values array
must be non-empty. If the operator is Exists
or DoesNotExist, the values array must be
empty. If the operator is Gt or Lt, the values
array must have a single element, which will
be interpreted as an integer. This array is
replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
type: object
weight:
description: Weight associated with matching the corresponding
nodeSelectorTerm, in the range 1-100.
format: int32
type: integer
required:
- preference
- weight
type: object
type: array
requiredDuringSchedulingIgnoredDuringExecution:
description: If the affinity requirements specified by this
field are not met at scheduling time, the pod will not be
scheduled onto the node. If the affinity requirements specified
by this field cease to be met at some point during pod execution
(e.g. due to an update), the system may or may not try to
eventually evict the pod from its node.
properties:
nodeSelectorTerms:
description: Required. A list of node selector terms. The
terms are ORed.
items:
description: A null or empty node selector term matches
no objects. The requirements of them are ANDed. The
TopologySelectorTerm type implements a subset of the
NodeSelectorTerm.
properties:
matchExpressions:
description: A list of node selector requirements
by node's labels.
items:
description: A node selector requirement is a selector
that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: The label key that the selector
applies to.
type: string
operator:
description: Represents a key's relationship
to a set of values. Valid operators are In,
NotIn, Exists, DoesNotExist. Gt, and Lt.
type: string
values:
description: An array of string values. If the
operator is In or NotIn, the values array
must be non-empty. If the operator is Exists
or DoesNotExist, the values array must be
empty. If the operator is Gt or Lt, the values
array must have a single element, which will
be interpreted as an integer. This array is
replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchFields:
description: A list of node selector requirements
by node's fields.
items:
description: A node selector requirement is a selector
that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: The label key that the selector
applies to.
type: string
operator:
description: Represents a key's relationship
to a set of values. Valid operators are In,
NotIn, Exists, DoesNotExist. Gt, and Lt.
type: string
values:
description: An array of string values. If the
operator is In or NotIn, the values array
must be non-empty. If the operator is Exists
or DoesNotExist, the values array must be
empty. If the operator is Gt or Lt, the values
array must have a single element, which will
be interpreted as an integer. This array is
replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
type: object
type: array
required:
- nodeSelectorTerms
type: object
type: object
podAffinity:
description: Describes pod affinity scheduling rules (e.g. co-locate
this pod in the same node, zone, etc. as some other pod(s)).
properties:
preferredDuringSchedulingIgnoredDuringExecution:
description: The scheduler will prefer to schedule pods to nodes
that satisfy the affinity expressions specified by this field,
but it may choose a node that violates one or more of the
expressions. The node that is most preferred is the one with
the greatest sum of weights, i.e. for each node that meets
all of the scheduling requirements (resource request, requiredDuringScheduling
affinity expressions, etc.), compute a sum by iterating through
the elements of this field and adding "weight" to the sum
if the node has pods which matches the corresponding podAffinityTerm;
the node(s) with the highest sum are the most preferred.
items:
description: The weights of all of the matched WeightedPodAffinityTerm
fields are added per-node to find the most preferred node(s)
properties:
podAffinityTerm:
description: Required. A pod affinity term, associated
with the corresponding weight.
properties:
labelSelector:
description: A label query over a set of resources,
in this case pods.
properties:
matchExpressions:
description: matchExpressions is a list of label
selector requirements. The requirements are
ANDed.
items:
description: A label selector requirement is
a selector that contains values, a key, and
an operator that relates the key and values.
properties:
key:
description: key is the label key that the
selector applies to.
type: string
operator:
description: operator represents a key's
relationship to a set of values. Valid
operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string
values. If the operator is In or NotIn,
the values array must be non-empty. If
the operator is Exists or DoesNotExist,
the values array must be empty. This array
is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value}
pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions,
whose key field is "key", the operator is "In",
and the values array contains only "value".
The requirements are ANDed.
type: object
type: object
namespaces:
description: namespaces specifies which namespaces
the labelSelector applies to (matches against);
null or empty list means "this pod's namespace"
items:
type: string
type: array
topologyKey:
description: This pod should be co-located (affinity)
or not co-located (anti-affinity) with the pods
matching the labelSelector in the specified namespaces,
where co-located is defined as running on a node
whose value of the label with key topologyKey matches
that of any node on which any of the selected pods
is running. Empty topologyKey is not allowed.
type: string
required:
- topologyKey
type: object
weight:
description: weight associated with matching the corresponding
podAffinityTerm, in the range 1-100.
format: int32
type: integer
required:
- podAffinityTerm
- weight
type: object
type: array
requiredDuringSchedulingIgnoredDuringExecution:
description: If the affinity requirements specified by this
field are not met at scheduling time, the pod will not be
scheduled onto the node. If the affinity requirements specified
by this field cease to be met at some point during pod execution
(e.g. due to a pod label update), the system may or may not
try to eventually evict the pod from its node. When there
are multiple elements, the lists of nodes corresponding to
each podAffinityTerm are intersected, i.e. all terms must
be satisfied.
items:
description: Defines a set of pods (namely those matching
the labelSelector relative to the given namespace(s)) that
this pod should be co-located (affinity) or not co-located
(anti-affinity) with, where co-located is defined as running
on a node whose value of the label with key <topologyKey>
matches that of any node on which a pod of the set of pods
is running
properties:
labelSelector:
description: A label query over a set of resources, in
this case pods.
properties:
matchExpressions:
description: matchExpressions is a list of label selector
requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector
that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector
applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are In,
NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values.
If the operator is In or NotIn, the values
array must be non-empty. If the operator is
Exists or DoesNotExist, the values array must
be empty. This array is replaced during a
strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs.
A single {key,value} in the matchLabels map is equivalent
to an element of matchExpressions, whose key field
is "key", the operator is "In", and the values array
contains only "value". The requirements are ANDed.
type: object
type: object
namespaces:
description: namespaces specifies which namespaces the
labelSelector applies to (matches against); null or
empty list means "this pod's namespace"
items:
type: string
type: array
topologyKey:
description: This pod should be co-located (affinity)
or not co-located (anti-affinity) with the pods matching
the labelSelector in the specified namespaces, where
co-located is defined as running on a node whose value
of the label with key topologyKey matches that of any
node on which any of the selected pods is running. Empty
topologyKey is not allowed.
type: string
required:
- topologyKey
type: object
type: array
type: object
podAntiAffinity:
description: Describes pod anti-affinity scheduling rules (e.g.
avoid putting this pod in the same node, zone, etc. as some other
pod(s)).
properties:
preferredDuringSchedulingIgnoredDuringExecution:
description: The scheduler will prefer to schedule pods to nodes
that satisfy the anti-affinity expressions specified by this
field, but it may choose a node that violates one or more
of the expressions. The node that is most preferred is the
one with the greatest sum of weights, i.e. for each node that
meets all of the scheduling requirements (resource request,
requiredDuringScheduling anti-affinity expressions, etc.),
compute a sum by iterating through the elements of this field
and adding "weight" to the sum if the node has pods which
matches the corresponding podAffinityTerm; the node(s) with
the highest sum are the most preferred.
items:
description: The weights of all of the matched WeightedPodAffinityTerm
fields are added per-node to find the most preferred node(s)
properties:
podAffinityTerm:
description: Required. A pod affinity term, associated
with the corresponding weight.
properties:
labelSelector:
description: A label query over a set of resources,
in this case pods.
properties:
matchExpressions:
description: matchExpressions is a list of label
selector requirements. The requirements are
ANDed.
items:
description: A label selector requirement is
a selector that contains values, a key, and
an operator that relates the key and values.
properties:
key:
description: key is the label key that the
selector applies to.
type: string
operator:
description: operator represents a key's
relationship to a set of values. Valid
operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string
values. If the operator is In or NotIn,
the values array must be non-empty. If
the operator is Exists or DoesNotExist,
the values array must be empty. This array
is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value}
pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions,
whose key field is "key", the operator is "In",
and the values array contains only "value".
The requirements are ANDed.
type: object
type: object
namespaces:
description: namespaces specifies which namespaces
the labelSelector applies to (matches against);
null or empty list means "this pod's namespace"
items:
type: string
type: array
topologyKey:
description: This pod should be co-located (affinity)
or not co-located (anti-affinity) with the pods
matching the labelSelector in the specified namespaces,
where co-located is defined as running on a node
whose value of the label with key topologyKey matches
that of any node on which any of the selected pods
is running. Empty topologyKey is not allowed.
type: string
required:
- topologyKey
type: object
weight:
description: weight associated with matching the corresponding
podAffinityTerm, in the range 1-100.
format: int32
type: integer
required:
- podAffinityTerm
- weight
type: object
type: array
requiredDuringSchedulingIgnoredDuringExecution:
description: If the anti-affinity requirements specified by
this field are not met at scheduling time, the pod will not
be scheduled onto the node. If the anti-affinity requirements
specified by this field cease to be met at some point during
pod execution (e.g. due to a pod label update), the system
may or may not try to eventually evict the pod from its node.
When there are multiple elements, the lists of nodes corresponding
to each podAffinityTerm are intersected, i.e. all terms must
be satisfied.
items:
description: Defines a set of pods (namely those matching
the labelSelector relative to the given namespace(s)) that
this pod should be co-located (affinity) or not co-located
(anti-affinity) with, where co-located is defined as running
on a node whose value of the label with key <topologyKey>
matches that of any node on which a pod of the set of pods
is running
properties:
labelSelector:
description: A label query over a set of resources, in
this case pods.
properties:
matchExpressions:
description: matchExpressions is a list of label selector
requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector
that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector
applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are In,
NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values.
If the operator is In or NotIn, the values
array must be non-empty. If the operator is
Exists or DoesNotExist, the values array must
be empty. This array is replaced during a
strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs.
A single {key,value} in the matchLabels map is equivalent
to an element of matchExpressions, whose key field
is "key", the operator is "In", and the values array
contains only "value". The requirements are ANDed.
type: object
type: object
namespaces:
description: namespaces specifies which namespaces the
labelSelector applies to (matches against); null or
empty list means "this pod's namespace"
items:
type: string
type: array
topologyKey:
description: This pod should be co-located (affinity)
or not co-located (anti-affinity) with the pods matching
the labelSelector in the specified namespaces, where
co-located is defined as running on a node whose value
of the label with key topologyKey matches that of any
node on which any of the selected pods is running. Empty
topologyKey is not allowed.
type: string
required:
- topologyKey
type: object
type: array
type: object
type: object
customCommand:
items:
type: string
type: array
image:
type: string
labels:
additionalProperties:
type: string
type: object
nodeSelector:
additionalProperties:
type: string
type: object
podLimits:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: ResourceList is a set of (resource name, quantity) pairs.
type: object
podRequests:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: ResourceList is a set of (resource name, quantity) pairs.
type: object
tolerations:
items:
description: The pod this Toleration is attached to tolerates any
taint that matches the triple <key,value,effect> using the matching
operator <operator>.
properties:
effect:
description: Effect indicates the taint effect to match. Empty
means match all taint effects. When specified, allowed values
are NoSchedule, PreferNoSchedule and NoExecute.
type: string
key:
description: Key is the taint key that the toleration applies
to. Empty means match all taint keys. If the key is empty, operator
must be Exists; this combination means to match all values and
all keys.
type: string
operator:
description: Operator represents a key's relationship to the value.
Valid operators are Exists and Equal. Defaults to Equal. Exists
is equivalent to wildcard for value, so that a pod can tolerate
all taints of a particular category.
type: string
tolerationSeconds:
description: TolerationSeconds represents the period of time the
toleration (which must be of effect NoExecute, otherwise this
field is ignored) tolerates the taint. By default, it is not
set, which means tolerate the taint forever (do not evict).
Zero and negative values will be treated as 0 (evict immediately)
by the system.
format: int64
type: integer
value:
description: Value is the taint value the toleration matches to.
If the operator is Exists, the value should be empty, otherwise
just a regular string.
type: string
type: object
type: array
version:
type: string
required:
- image
- version
type: object
status:
description: WarmImageStatus defines the observed state of WarmImage
type: object
type: object
version: v1alpha1
versions:
- name: v1alpha1
served: true
storage: true

+ 33
- 0
deploy/operator.yaml View File

@@ -0,0 +1,33 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: warm-image-operator
spec:
replicas: 1
selector:
matchLabels:
name: warm-image-operator
template:
metadata:
labels:
name: warm-image-operator
spec:
serviceAccountName: warm-image-operator
containers:
- name: warm-image-operator
# Replace this with the built image name
image: REPLACE_IMAGE
command:
- warm-image-operator
imagePullPolicy: Always
env:
- name: WATCH_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: OPERATOR_NAME
value: "warm-image-operator"

+ 80
- 0
deploy/role.yaml View File

@@ -0,0 +1,80 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
creationTimestamp: null
name: warm-image-operator
rules:
- apiGroups:
- ""
resources:
- pods
- services
- services/finalizers
- endpoints
- persistentvolumeclaims
- events
- configmaps
- secrets
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- apps
resources:
- deployments
- daemonsets
- replicasets
- statefulsets
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- monitoring.coreos.com
resources:
- servicemonitors
verbs:
- get
- create
- apiGroups:
- apps
resourceNames:
- warm-image-operator
resources:
- deployments/finalizers
verbs:
- update
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- apiGroups:
- apps
resources:
- replicasets
- deployments
verbs:
- get
- apiGroups:
- blindage.org
resources:
- '*'
verbs:
- create
- delete
- get
- list
- patch
- update
- watch

+ 11
- 0
deploy/role_binding.yaml View File

@@ -0,0 +1,11 @@
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: warm-image-operator
subjects:
- kind: ServiceAccount
name: warm-image-operator
roleRef:
kind: Role
name: warm-image-operator
apiGroup: rbac.authorization.k8s.io

+ 4
- 0
deploy/service_account.yaml View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: warm-image-operator

+ 19
- 0
go.mod View File

@@ -0,0 +1,19 @@
module git.blindage.org/21h/warm-image-operator

go 1.13

require (
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 // indirect
github.com/getsentry/raven-go v0.2.0
github.com/operator-framework/operator-sdk v0.17.1
github.com/spf13/pflag v1.0.5
k8s.io/api v0.17.4
k8s.io/apimachinery v0.17.4
k8s.io/client-go v12.0.0+incompatible
sigs.k8s.io/controller-runtime v0.5.2
)

replace (
github.com/Azure/go-autorest => github.com/Azure/go-autorest v13.3.2+incompatible // Required by OLM
k8s.io/client-go => k8s.io/client-go v0.17.4 // Required by prometheus-operator
)

+ 1210
- 0
go.sum
File diff suppressed because it is too large
View File


+ 10
- 0
pkg/apis/addtoscheme_blindage_v1alpha1.go View File

@@ -0,0 +1,10 @@
package apis

import (
"git.blindage.org/21h/warm-image-operator/pkg/apis/blindage/v1alpha1"
)

func init() {
// Register the types with the Scheme so the components can map objects to GroupVersionKinds and back
AddToSchemes = append(AddToSchemes, v1alpha1.SchemeBuilder.AddToScheme)
}

+ 13
- 0
pkg/apis/apis.go View File

@@ -0,0 +1,13 @@
package apis

import (
"k8s.io/apimachinery/pkg/runtime"
)

// AddToSchemes may be used to add all resources defined in the project to a Scheme
var AddToSchemes runtime.SchemeBuilder

// AddToScheme adds all Resources to the Scheme
func AddToScheme(s *runtime.Scheme) error {
return AddToSchemes.AddToScheme(s)
}

+ 6
- 0
pkg/apis/blindage/group.go View File

@@ -0,0 +1,6 @@
// Package blindage contains blindage API versions.
//
// This file ensures Go source parsers acknowledge the blindage package
// and any child packages. It can be removed if any other Go source files are
// added to this package.
package blindage

+ 4
- 0
pkg/apis/blindage/v1alpha1/doc.go View File

@@ -0,0 +1,4 @@
// Package v1alpha1 contains API Schema definitions for the blindage v1alpha1 API group
// +k8s:deepcopy-gen=package,register
// +groupName=blindage.org
package v1alpha1

+ 19
- 0
pkg/apis/blindage/v1alpha1/register.go View File

@@ -0,0 +1,19 @@
// NOTE: Boilerplate only. Ignore this file.

// Package v1alpha1 contains API Schema definitions for the blindage v1alpha1 API group
// +k8s:deepcopy-gen=package,register
// +groupName=blindage.org
package v1alpha1

import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)

var (
// SchemeGroupVersion is group version used to register these objects
SchemeGroupVersion = schema.GroupVersion{Group: "blindage.org", Version: "v1alpha1"}

// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}
)

+ 48
- 0
pkg/apis/blindage/v1alpha1/warmimage_types.go View File

@@ -0,0 +1,48 @@
package v1alpha1

import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// WarmImageSpec defines the desired state of WarmImage
type WarmImageSpec struct {
Image string `json:"image"` // image url
Version string `json:"version"` // image tag
Labels map[string]string `json:"labels,omitempty"` // add additional labels
CustomCommand []string `json:"customCommand,omitempty"` // by default runs sh with infinite loop
NodeSelector *map[string]string `json:"nodeSelector,omitempty"`
Tolerations *[]corev1.Toleration `json:"tolerations,omitempty"`
Affinity *corev1.Affinity `json:"affinity,omitempty"`
PodRequests *corev1.ResourceList `json:"podRequests,omitempty"`
PodLimits *corev1.ResourceList `json:"podLimits,omitempty"`
}

// WarmImageStatus defines the observed state of WarmImage
type WarmImageStatus struct{}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// WarmImage is the Schema for the warmimages API
// +kubebuilder:subresource:status
// +kubebuilder:resource:path=warmimages,scope=Namespaced
type WarmImage struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec WarmImageSpec `json:"spec,omitempty"`
Status WarmImageStatus `json:"status,omitempty"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// WarmImageList contains a list of WarmImage
type WarmImageList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []WarmImage `json:"items"`
}

func init() {
SchemeBuilder.Register(&WarmImage{}, &WarmImageList{})
}

+ 165
- 0
pkg/apis/blindage/v1alpha1/zz_generated.deepcopy.go View File

@@ -0,0 +1,165 @@
// +build !ignore_autogenerated

// Code generated by operator-sdk. DO NOT EDIT.

package v1alpha1

import (
v1 "k8s.io/api/core/v1"
resource "k8s.io/apimachinery/pkg/api/resource"
runtime "k8s.io/apimachinery/pkg/runtime"
)

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WarmImage) DeepCopyInto(out *WarmImage) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WarmImage.
func (in *WarmImage) DeepCopy() *WarmImage {
if in == nil {
return nil
}
out := new(WarmImage)
in.DeepCopyInto(out)
return out
}

// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *WarmImage) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WarmImageList) DeepCopyInto(out *WarmImageList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]WarmImage, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WarmImageList.
func (in *WarmImageList) DeepCopy() *WarmImageList {
if in == nil {
return nil
}
out := new(WarmImageList)
in.DeepCopyInto(out)
return out
}

// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *WarmImageList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WarmImageSpec) DeepCopyInto(out *WarmImageSpec) {
*out = *in
if in.Labels != nil {
in, out := &in.Labels, &out.Labels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.CustomCommand != nil {
in, out := &in.CustomCommand, &out.CustomCommand
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.NodeSelector != nil {
in, out := &in.NodeSelector, &out.NodeSelector
*out = new(map[string]string)
if **in != nil {
in, out := *in, *out
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
if in.Tolerations != nil {
in, out := &in.Tolerations, &out.Tolerations
*out = new([]v1.Toleration)
if **in != nil {
in, out := *in, *out
*out = make([]v1.Toleration, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
if in.Affinity != nil {
in, out := &in.Affinity, &out.Affinity
*out = new(v1.Affinity)
(*in).DeepCopyInto(*out)
}
if in.PodRequests != nil {
in, out := &in.PodRequests, &out.PodRequests
*out = new(v1.ResourceList)
if **in != nil {
in, out := *in, *out
*out = make(map[v1.ResourceName]resource.Quantity, len(*in))
for key, val := range *in {
(*out)[key] = val.DeepCopy()
}
}
}
if in.PodLimits != nil {
in, out := &in.PodLimits, &out.PodLimits
*out = new(v1.ResourceList)
if **in != nil {
in, out := *in, *out
*out = make(map[v1.ResourceName]resource.Quantity, len(*in))
for key, val := range *in {
(*out)[key] = val.DeepCopy()
}
}
}
return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WarmImageSpec.
func (in *WarmImageSpec) DeepCopy() *WarmImageSpec {
if in == nil {
return nil
}
out := new(WarmImageSpec)
in.DeepCopyInto(out)
return out
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WarmImageStatus) DeepCopyInto(out *WarmImageStatus) {
*out = *in
return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WarmImageStatus.
func (in *WarmImageStatus) DeepCopy() *WarmImageStatus {
if in == nil {
return nil
}
out := new(WarmImageStatus)
in.DeepCopyInto(out)
return out
}

+ 10
- 0
pkg/controller/add_warmimage.go View File

@@ -0,0 +1,10 @@
package controller

import (
"git.blindage.org/21h/warm-image-operator/pkg/controller/warmimage"
)

func init() {
// AddToManagerFuncs is a list of functions to create controllers and add them to a manager.
AddToManagerFuncs = append(AddToManagerFuncs, warmimage.Add)
}

+ 18
- 0
pkg/controller/controller.go View File

@@ -0,0 +1,18 @@
package controller

import (
"sigs.k8s.io/controller-runtime/pkg/manager"
)

// AddToManagerFuncs is a list of functions to add all Controllers to the Manager
var AddToManagerFuncs []func(manager.Manager) error

// AddToManager adds all Controllers to the Manager
func AddToManager(m manager.Manager) error {
for _, f := range AddToManagerFuncs {
if err := f(m); err != nil {
return err
}
}
return nil
}

+ 93
- 0
pkg/controller/warmimage/manifests.go View File

@@ -0,0 +1,93 @@
package warmimage

import (
"fmt"

blindagev1alpha1 "git.blindage.org/21h/warm-image-operator/pkg/apis/blindage/v1alpha1"
v1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func generateDaemonSet(cr *blindagev1alpha1.WarmImage) v1.DaemonSet {

// check affinity rules
affinity := &corev1.Affinity{}
if cr.Spec.Affinity != nil {
affinity = cr.Spec.Affinity
}

resourcesLimits := corev1.ResourceList{}
if cr.Spec.PodLimits != nil {
resourcesLimits = *cr.Spec.PodLimits
}

resourcesRequests := corev1.ResourceList{}
if cr.Spec.PodRequests != nil {
resourcesRequests = *cr.Spec.PodRequests
}

image := ""
if cr.Spec.Image != "" && cr.Spec.Version != "" {
image = fmt.Sprintf("%v:%v", cr.Spec.Image, cr.Spec.Version)
}

command := []string{
"/bin/sh",
"-c",
"while true; do sleep 86400; done",
}
if len(cr.Spec.CustomCommand) > 0 {
command = cr.Spec.CustomCommand
}

pullPolicy := corev1.PullIfNotPresent

daemonSet := v1.DaemonSet{
ObjectMeta: metav1.ObjectMeta{
Name: cr.Name,
Namespace: cr.ObjectMeta.Namespace,
Labels: mergeLabels(baseLabels(cr), cr.Spec.Labels),
},
Spec: v1.DaemonSetSpec{
Selector: &metav1.LabelSelector{
MatchLabels: mergeLabels(baseLabels(cr), cr.Spec.Labels,
map[string]string{"component": "application"},
),
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: mergeLabels(baseLabels(cr), cr.Spec.Labels,
map[string]string{"component": "application"},
),
},

Spec: corev1.PodSpec{
Affinity: affinity,
Containers: []corev1.Container{
{
Name: "loop",
Image: image,
Command: command,
ImagePullPolicy: pullPolicy,
Resources: corev1.ResourceRequirements{
Limits: resourcesLimits,
Requests: resourcesRequests,
},
},
},
},
},
},
}

if cr.Spec.NodeSelector != nil {
daemonSet.Spec.Template.Spec.NodeSelector = *cr.Spec.NodeSelector
}

if cr.Spec.Tolerations != nil {
daemonSet.Spec.Template.Spec.Tolerations = *cr.Spec.Tolerations
}

return daemonSet
}

+ 31
- 0
pkg/controller/warmimage/reconcile.go View File

@@ -0,0 +1,31 @@
package warmimage

import (
"reflect"

v1 "k8s.io/api/apps/v1"
)

// check if reconcile required
func reconcileDaemonSet(foundDaemonSet v1.DaemonSet, newDaemonSet v1.DaemonSet) (bool, v1.DaemonSet) {

reconcileRequired := false

if !reflect.DeepEqual(foundDaemonSet.Labels, newDaemonSet.Labels) {
foundDaemonSet.Labels = newDaemonSet.Labels
reconcileRequired = true
}

if !reflect.DeepEqual(foundDaemonSet.Spec.Template, newDaemonSet.Spec.Template) {
foundDaemonSet.Spec.Template = newDaemonSet.Spec.Template
reconcileRequired = true
}

if !reflect.DeepEqual(foundDaemonSet.Annotations, newDaemonSet.Annotations) {
foundDaemonSet.Annotations = newDaemonSet.Annotations
reconcileRequired = true
}

return reconcileRequired, foundDaemonSet

}

+ 40
- 0
pkg/controller/warmimage/utils.go View File

@@ -0,0 +1,40 @@
package warmimage

import (
blindagev1alpha1 "git.blindage.org/21h/warm-image-operator/pkg/apis/blindage/v1alpha1"
v1 "k8s.io/api/core/v1"
)

func mergeLabels(itermaps ...map[string]string) map[string]string {
result := make(map[string]string)
for _, rv := range itermaps {
for k, v := range rv {
result[k] = v
}
}
return result
}

func baseLabels(cr *blindagev1alpha1.WarmImage) map[string]string {
return map[string]string{
"operator": "tagmanager-operator",
"instance": cr.Name,
}
}

func mergeENV(envs []v1.EnvVar) []v1.EnvVar {
return append([]v1.EnvVar{
v1.EnvVar{
Name: "POD_NAME",
ValueFrom: &v1.EnvVarSource{
FieldRef: &v1.ObjectFieldSelector{FieldPath: "metadata.name"},
},
},
v1.EnvVar{
Name: "POD_NAMESPACE",
ValueFrom: &v1.EnvVarSource{
FieldRef: &v1.ObjectFieldSelector{FieldPath: "metadata.namespace"},
},
},
}, envs...)
}

+ 138
- 0
pkg/controller/warmimage/warmimage_controller.go View File

@@ -0,0 +1,138 @@
package warmimage

import (
"context"

blindagev1alpha1 "git.blindage.org/21h/warm-image-operator/pkg/apis/blindage/v1alpha1"
"github.com/getsentry/raven-go"
v1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
)

var log = logf.Log.WithName("controller_warmimage")

/**
* USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller
* business logic. Delete these comments after modifying this file.*
*/

// Add creates a new WarmImage Controller and adds it to the Manager. The Manager will set fields on the Controller
// and Start it when the Manager is Started.
func Add(mgr manager.Manager) error {
return add(mgr, newReconciler(mgr))
}

// newReconciler returns a new reconcile.Reconciler
func newReconciler(mgr manager.Manager) reconcile.Reconciler {
return &ReconcileWarmImage{client: mgr.GetClient(), scheme: mgr.GetScheme()}
}

// add adds a new Controller to mgr with r as the reconcile.Reconciler
func add(mgr manager.Manager, r reconcile.Reconciler) error {
// Create a new controller
c, err := controller.New("warmimage-controller", mgr, controller.Options{Reconciler: r})
if err != nil {
return err
}

// Watch for changes to primary resource WarmImage
err = c.Watch(&source.Kind{Type: &blindagev1alpha1.WarmImage{}}, &handler.EnqueueRequestForObject{})
if err != nil {
return err
}

// TODO(user): Modify this to be the types you create that are owned by the primary resource
// Watch for changes to secondary resource Pods and requeue the owner WarmImage
err = c.Watch(&source.Kind{Type: &corev1.Pod{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &blindagev1alpha1.WarmImage{},
})
if err != nil {
return err
}

return nil
}

// blank assignment to verify that ReconcileWarmImage implements reconcile.Reconciler
var _ reconcile.Reconciler = &ReconcileWarmImage{}

// ReconcileWarmImage reconciles a WarmImage object
type ReconcileWarmImage struct {
// This client, initialized using mgr.Client() above, is a split client
// that reads objects from the cache and writes to the apiserver
client client.Client
scheme *runtime.Scheme
}

// Reconcile reads that state of the cluster for a WarmImage object and makes changes based on the state read
// and what is in the WarmImage.Spec
// TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates
// a Pod as an example
// Note:
// The Controller will requeue the Request to be processed again if the returned error is non-nil or
// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
func (r *ReconcileWarmImage) Reconcile(request reconcile.Request) (reconcile.Result, error) {
reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
reqLogger.Info("Reconciling WarmImage")

// Fetch the WarmImage instance
instance := &blindagev1alpha1.WarmImage{}
err := r.client.Get(context.TODO(), request.NamespacedName, instance)
if err != nil {
if errors.IsNotFound(err) {
// Request object not found, could have been deleted after reconcile request.
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
// Return and don't requeue
return reconcile.Result{}, nil
}
// Error reading the object - requeue the request.
return reconcile.Result{}, err
}

// reconcile DaemonSet
newDaemonSet := generateDaemonSet(instance)

if err := controllerutil.SetControllerReference(instance, &newDaemonSet, r.scheme); err != nil {
raven.CaptureErrorAndWait(err, nil)
return reconcile.Result{}, err
}

// controller DaemonSet
foundDaemonSet := v1.DaemonSet{}
err = r.client.Get(context.TODO(), types.NamespacedName{Name: newDaemonSet.Name, Namespace: newDaemonSet.Namespace}, &foundDaemonSet)
if err != nil && errors.IsNotFound(err) {
reqLogger.Info("Creating a new DaemonSet", "Namespace", newDaemonSet.Namespace, "Name", newDaemonSet.Name)
err = r.client.Create(context.TODO(), &newDaemonSet)
if err != nil {
reqLogger.Info("Creating DaemonSet error", "Namespace", newDaemonSet.Namespace, "Name", newDaemonSet.Name, "Error", err)
raven.CaptureErrorAndWait(err, nil)
return reconcile.Result{}, err
}
} else if err != nil {
raven.CaptureErrorAndWait(err, nil)
return reconcile.Result{}, err
} else {
if reconcileRequired, reconDaemonSet := reconcileDaemonSet(foundDaemonSet, newDaemonSet); reconcileRequired {
reqLogger.Info("Updating DaemonSet", "Namespace", reconDaemonSet.Namespace, "Name", reconDaemonSet.Name)
if err = r.client.Update(context.TODO(), &reconDaemonSet); err != nil {
reqLogger.Info("Reconcile DaemonSet error", "Namespace", foundDaemonSet.Namespace, "Name", foundDaemonSet.Name, "Error", err)
raven.CaptureErrorAndWait(err, nil)
return reconcile.Result{}, err
}
}
}

return reconcile.Result{}, nil
}

+ 5
- 0
tools.go View File

@@ -0,0 +1,5 @@
// +build tools

// Place any runtime dependencies as imports in this file.
// Go modules will be forced to download and install them.
package tools

+ 202
- 0
vendor/cloud.google.com/go/LICENSE View File

@@ -0,0 +1,202 @@

Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.

"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.

"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.

"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.

"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.

"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).

"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.

"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.

2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.

3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:

(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and

(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and

(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and

(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.

You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.