Commit 4bac5c29 authored by Tim's avatar Tim

Initial Commit

parents
Pipeline #116 failed with stages
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
bin
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Kubernetes Generated files - skip generated files, except for vendored files
!vendor/**/zz_generated.*
# editor and IDE paraphernalia
.idea
*.swp
*.swo
*~
# Build the manager binary
FROM golang:1.12.5 as builder
WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
# cache deps before building and copying source so that we don't need to re-download as much
# and so that source changes don't invalidate our downloaded layer
RUN go mod download
# Copy the go source
COPY main.go main.go
COPY api/ api/
COPY controllers/ controllers/
# Build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go
# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:latest
WORKDIR /
COPY --from=builder /workspace/manager .
ENTRYPOINT ["/manager"]
# Image URL to use all building/pushing image targets
IMG ?= controller:latest
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
CRD_OPTIONS ?= "crd:trivialVersions=true"
all: manager
# Run tests
test: generate fmt vet manifests
go test ./api/... ./controllers/... -coverprofile cover.out
# Build manager binary
manager: generate fmt vet
go build -o bin/manager main.go
# Run against the configured Kubernetes cluster in ~/.kube/config
run: generate fmt vet
go run ./main.go
# Install CRDs into a cluster
install: manifests
kubectl apply -f config/crd/bases
# Deploy controller in the configured Kubernetes cluster in ~/.kube/config
deploy: manifests
kubectl apply -f config/crd/bases
kustomize build config/default | kubectl apply -f -
# Generate manifests e.g. CRD, RBAC etc.
manifests: controller-gen
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
# Run go fmt against code
fmt:
go fmt ./...
# Run go vet against code
vet:
go vet ./...
# Generate code
generate: controller-gen
$(CONTROLLER_GEN) object:headerFile=./hack/boilerplate.go.txt paths=./api/...
# Build the docker image
docker-build: test
docker build . -t ${IMG}
@echo "updating kustomize image patch file for manager resource"
sed -i'' -e 's@image: .*@image: '"${IMG}"'@' ./config/default/manager_image_patch.yaml
# Push the docker image
docker-push:
docker push ${IMG}
# find or download controller-gen
# download controller-gen if necessary
controller-gen:
ifeq (, $(shell which controller-gen))
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.0-beta.2
CONTROLLER_GEN=$(shell go env GOPATH)/bin/controller-gen
else
CONTROLLER_GEN=$(shell which controller-gen)
endif
version: "2"
domain: libre.sh
repo: test
resources:
- group: app
version: v1
kind: Myapp
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package v1 contains API Schema definitions for the app v1 API group
// +kubebuilder:object:generate=true
// +groupName=app.libre.sh
package v1
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)
var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "app.libre.sh", Version: "v1"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
import (
"fmt"
"k8s.io/apimachinery/pkg/labels"
)
type component struct {
name string // eg. web, database, cache
objNameFmt string
objName string
}
var (
// WordpressSecret component
AppCron = component{name: "web", objNameFmt: "%s-wp"}
)
// ComponentName returns the object name for a component
func (a Myapp) ComponentName(component component) string {
name := component.objName
if len(component.objNameFmt) > 0 {
name = fmt.Sprintf(component.objNameFmt, a.ObjectMeta.Name)
}
// if component == AppDBUpgrade {
// name = fmt.Sprintf("%s-for-%s", name, a.ImageVersion())
// }
return name
}
func (a Myapp) OffshootName() string {
return a.Name
}
func (a Myapp) ServiceName() string {
return a.OffshootName()
}
func (a Myapp) DBServiceName() string {
if len(a.Spec.DatabaseService) > 0 {
return a.Spec.DatabaseService
}
return a.OffshootName()
}
func (a Myapp) RedisServiceName() string {
if len(a.Spec.DatabaseService) > 0 {
return a.Spec.DatabaseService
}
return a.OffshootName()
}
func (o *Myapp) Labels() labels.Set {
partOf := "myApp"
if o.ObjectMeta.Labels != nil && len(o.ObjectMeta.Labels["app.kubernetes.io/part-of"]) > 0 {
partOf = o.ObjectMeta.Labels["app.kubernetes.io/part-of"]
}
labels := labels.Set{
"app.kubernetes.io/name": "myApp",
"app.kubernetes.io/part-of": partOf,
"app.kubernetes.io/instance": o.ObjectMeta.Name,
"app.kubernetes.io/managed-by": "myapp-operator.libre.sh",
}
return labels
}
// func (a Myapp) image() string {
// return fmt.Sprintf("%s:%s", a.Spec.Image, a.Spec.Tag)
// }
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
//appcat "kmodules.xyz/custom-resources/apis/appcatalog/v1alpha1" // ISSUE with client-go
)
const (
ResourceCodeApp = "app"
ResourceKindApp = "App"
ResourceSingularApp = "app"
ResourcePluralApp = "apps"
)
// SecretRef represents a reference to a Secret
type SecretRef string
// Domain represents a valid domain name
type Domain string
// type AppVersion string
// type OperatorVersion string
// MyappSpec defines the desired state of Myapp
type MyappSpec struct {
// Number of instances to deploy for your App.
Replicas *int32 `json:"replicas,omitempty"`
Domains []Domain `json:"domains,omitempty"`
Storage StorageSpec `json:"storage"`
AppVersion string `json:"appVersion,omitempty"`
OperatorVersion string `json:"operatorVersion,omitempty"`
// WordPress runtime image to use. Defaults to quay.io/presslabs/wordpress-runtime:<latest stable runtime tag>
// +optional
Image string `json:"image,omitempty"`
// DatabaseBinding *appcat.AppReference `json:"databaseBinding,omitempty"`
// Database authentication secret
DatabaseSecret *corev1.SecretVolumeSource `json:"databaseSecret,omitempty"`
DatabaseService string `json:"databaseService,omitempty"`
// Database authentication secret
SMTPSecret *corev1.SecretVolumeSource `json:"SMTPSecret,omitempty"`
// EmailTransport EmailTransportSpec `json:"emailTransport"`
// Auth AuthSpec `json:"auth"`
// TLSSecretRef a secret containing the TLS certificates for this site.
// +optional
TLSSecretRef SecretRef `json:"tlsSecretRef,omitempty"`
// Init is used to initialize database
// +optional
// Init *InitSpec `json:"init,omitempty"`
// PodTemplate is an optional configuration for pods used to expose database
// +optional
PodTemplate *corev1.PodTemplateSpec `json:"podTemplate,omitempty"`
ServiceTemplate *corev1.Service `json:"serviceTemplate,omitempty"`
// BackupSchedule spec to specify how database backup will be taken
// +optional
// *BackupScheduleSpec `json:"backupSchedule,omitempty"`
TerminationPolicy TerminationPolicy `json:"terminationPolicy,omitempty"`
}
type TerminationPolicy string
const (
// Pauses database into a DormantDatabase
TerminationPolicyPause TerminationPolicy = "Pause"
// Deletes database pods, service, pvcs but leave the snapshot data intact. This will not create a DormantDatabase.
TerminationPolicyDelete TerminationPolicy = "Delete"
// Deletes database pods, service, pvcs and snapshot data. This will not create a DormantDatabase.
TerminationPolicyWipeOut TerminationPolicy = "WipeOut"
// Rejects attempt to delete database using ValidationWebhook. This replaces spec.doNotPause = true
TerminationPolicyDoNotTerminate TerminationPolicy = "DoNotTerminate"
)
// type AuthSpec struct {
// Username string `json:"username"`
// Password string `json:"username"`
// }
// type BackupScheduleSpec struct {
// CronExpression string `json:"cronExpression,omitempty"`
// Snapshot Spec
// store.Backend `json:",inline"`
// StorageType can be durable or ephemeral.
// If not given, database storage type will be used.
// +optional
// StorageType *StorageType `json:"storageType,omitempty"`
// PodTemplate is an optional configuration for pods used to take database snapshots
// +optional
// PodTemplate ofst.PodTemplateSpec `json:"podTemplate,omitempty"`
// PodVolumeClaimSpec is used to specify temporary storage for backup/restore Job.
// If not given, database's PvcSpec will be used.
// If storageType is durable, then a PVC will be created using this PVCSpec.
// If storageType is ephemeral, then an empty directory will be created of size PvcSpec.Resources.Requests[core.ResourceStorage].
// +optional
// PodVolumeClaimSpec *core.PersistentVolumeClaimSpec `json:"podVolumeClaimSpec,omitempty"`
// }
// type InitSpec struct {
// ScriptSource *ScriptSourceSpec `json:"scriptSource,omitempty"`
// Deprecated
// SnapshotSource *SnapshotSourceSpec `json:"snapshotSource,omitempty"`
// PostgresWAL *PostgresWALSourceSpec `json:"postgresWAL,omitempty"`
// Name of stash restoreSession in same namespace of kubedb object.
// ref: https://github.com/stashed/stash/blob/09af5d319bb5be889186965afb04045781d6f926/apis/stash/v1beta1/restore_session_types.go#L22
// StashRestoreSession *core.LocalObjectReference `json:"stashRestoreSession,omitempty"`
// POD
//}
type AppPhase string
const (
// used for Databases that are currently running
AppPhaseRunning AppPhase = "Running"
// used for Databases that are currently creating
AppPhaseCreating AppPhase = "Creating"
// used for Databases that are currently initializing
AppPhaseInitializing AppPhase = "Initializing"
// used for Databases that are currently initializing
AppPhaseUpgrading AppPhase = "Upgrading"
// used for Databases that are Failed
AppPhaseFailed AppPhase = "Failed"
)
// type StorageType string
// const (
// default storage type and requires spec.storage to be configured
// StorageTypeDurable StorageType = "Durable"
// Uses emptyDir as storage
// StorageTypeEphemeral StorageType = "Ephemeral"
//)
//type TerminationPolicy string
//const (
// Pauses database into a DormantDatabase
// TerminationPolicyPause TerminationPolicy = "Pause"
// Deletes database pods, service, pvcs but leave the snapshot data intact. This will not create a DormantDatabase.
// TerminationPolicyDelete TerminationPolicy = "Delete"
// Deletes database pods, service, pvcs and snapshot data. This will not create a DormantDatabase.
// TerminationPolicyWipeOut TerminationPolicy = "WipeOut"
// Rejects attempt to delete database using ValidationWebhook. This replaces spec.doNotPause = true
// TerminationPolicyDoNotTerminate TerminationPolicy = "DoNotTerminate"
//)
//type ScriptSourceSpec struct {
// ScriptPath string `json:"scriptPath,omitempty"`
// core.VolumeSource `json:",inline,omitempty"`
//}
//type SnapshotSourceSpec struct {
// Namespace string `json:"namespace"`
// Name string `json:"name"`
// Arguments to the restore job
// Args []string `json:"args,omitempty"`
//}
type StorageSpec struct {
// ReadOnly specifies if the volume should be mounted read-only inside the
// wordpress runtime container
ReadOnly bool `json:"readOnly,omitempty"`
// S3VolumeSource specifies the S3 object storage configuration for media
// files. It has the highest level of precedence over EmptyDir, HostPath
// and PersistentVolumeClaim
// +optional
S3VolumeSource *S3VolumeSource `json:"s3,omitempty"`
// PersistentVolumeClaim to use if no S3VolumeSource or GCSVolumeSource are
// specified
// +optional
PersistentVolumeClaim *corev1.PersistentVolumeClaimSpec `json:"persistentVolumeClaim,omitempty"`
// HostPath to use if no PersistentVolumeClaim is specified
// +optional
HostPath *corev1.HostPathVolumeSource `json:"hostPath,omitempty"`
// EmptyDir to use if no HostPath is specified
// +optional
EmptyDir *corev1.EmptyDirVolumeSource `json:"emptyDir,omitempty"`
}
// S3VolumeSource is the desired spec for accessing media files over S3
// compatible object store
type S3VolumeSource struct {
// Bucket for storing media files
// +kubebuilder:validation:MinLength=1
Bucket string `json:"bucket"`
// PathPrefix is the prefix for media files in bucket
PathPrefix string `json:"prefix,omitempty"`
// Env variables for accessing S3 bucket. Taken into account are:
// ACCESS_KEY, SECRET_ACCESS_KEY
// +optional
// +patchMergeKey=name
// +patchStrategy=merge
Env []corev1.EnvVar `json:"env,omitempty" patchStrategy:"merge" patchMergeKey:"name"`
}
// MyappStatus defines the observed state of Myapp
type MyappStatus struct {
AppVersion string `json:"appVersion,omitempty"`
OperatorVersion string `json:"operatorVersion,omitempty"`
AppPhase AppPhase `json:"appPhase,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// Myapp is the Schema for the myapps API
type Myapp struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec MyappSpec `json:"spec,omitempty"`
Status MyappStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// MyappList contains a list of Myapp
type MyappList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Myapp `json:"items"`
}
func init() {
SchemeBuilder.Register(&Myapp{}, &MyappList{})
}
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"golang.org/x/net/context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
// These tests are written in BDD-style using Ginkgo framework. Refer to
// http://onsi.github.io/ginkgo to learn more.
var _ = Describe("Myapp", func() {
var (
key types.NamespacedName
created, fetched *Myapp
)
BeforeEach(func() {
// Add any setup steps that needs to be executed before each test
})
AfterEach(func() {
// Add any teardown steps that needs to be executed after each test
})
// Add Tests for OpenAPI validation (or additonal CRD features) specified in
// your API definition.
// Avoid adding tests for vanilla CRUD operations because they would
// test Kubernetes API server, which isn't the goal here.
Context("Create API", func() {
It("should create an object successfully", func() {
key = types.NamespacedName{
Name: "foo",
Namespace: "default",
}
created = &Myapp{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "default",
}}
By("creating an API obj")
Expect(k8sClient.Create(context.TODO(), created)).To(Succeed())
fetched = &Myapp{}
Expect(k8sClient.Get(context.TODO(), key, fetched)).To(Succeed())
Expect(fetched).To(Equal(created))
By("deleting the created object")
Expect(k8sClient.Delete(context.TODO(), created)).To(Succeed())
Expect(k8sClient.Get(context.TODO(), key, created)).ToNot(Succeed())
})
})
})
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
import (
"path/filepath"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
)
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
var cfg *rest.Config
var k8sClient client.Client
var testEnv *envtest.Environment
func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecsWithDefaultAndCustomReporters(t,
"v1 Suite",
[]Reporter{envtest.NewlineReporter{}})
}
var _ = BeforeSuite(func(done Done) {
logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")},
}
err := SchemeBuilder.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
cfg, err = testEnv.Start()
Expect(err).ToNot(HaveOccurred())
Expect(cfg).ToNot(BeNil())
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).ToNot(HaveOccurred())
Expect(k8sClient).ToNot(BeNil())
close(done)
}, 60)
var _ = AfterSuite(func() {
By("tearing down the test environment")
err := testEnv.Stop()
Expect(err).ToNot(HaveOccurred())
})
// +build !ignore_autogenerated
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// autogenerated by controller-gen object, do not modify manually
package v1
import (
corev1 "k8s.io/api/core/v1"
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 *Myapp) DeepCopyInto(out *Myapp) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Myapp.
func (in *Myapp) DeepCopy() *Myapp {
if in == nil {
return nil
}
out := new(Myapp)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Myapp) 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 *MyappList) DeepCopyInto(out *MyappList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Myapp, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyappList.
func (in *MyappList) DeepCopy() *MyappList {
if in == nil {
return nil
}
out := new(MyappList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *MyappList) 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 *MyappSpec) DeepCopyInto(out *MyappSpec) {
*out = *in