Commit 34f4a6be authored by Konstantinos Samaras-Tsakiris's avatar Konstantinos Samaras-Tsakiris
Browse files

Merge branch 'unit-tests' into 'master'

Add Unit tests

Closes #44

See merge request !30
parents ef461d32 656392a1
Pipeline #2405969 passed with stages
in 4 minutes and 3 seconds
......@@ -26,3 +26,6 @@ bin
__*
secret.yaml
# Envtest bin files
testbin/*
......@@ -14,4 +14,6 @@ GoTest:
stage: go_test
image: golang:1.15
script:
- go test -v -mod=vendor ./...
\ No newline at end of file
- export CLUSTER_NAME=test
- export RUNTIME_REPO=https://gitlab.cern.ch/drupal/paas/drupal-runtime.git
- make test
\ No newline at end of file
# Change current shell to /bin/bash to support commands like 'source'
SHELL := /bin/bash
# Current Operator version
VERSION ?= 0.0.1
# Default bundle image tag
......@@ -30,7 +32,7 @@ ENVTEST_ASSETS_DIR=$(shell pwd)/testbin
test: generate fmt vet manifests
mkdir -p ${ENVTEST_ASSETS_DIR}
test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.0/hack/setup-envtest.sh
source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out
source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out -v -mod=vendor
# Build manager binary
manager: generate fmt vet
......
......@@ -17,8 +17,14 @@ limitations under the License.
package controllers
import (
"archive/tar"
"context"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/go-logr/logr"
buildv1 "github.com/openshift/api/build/v1"
......@@ -55,8 +61,33 @@ type DrupalSiteReconciler struct {
// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=*
// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;
func (r *DrupalSiteReconciler) initEnv() {
log := r.Log
log.Info("Initializing environment")
// <git_url>@<git_ref>
// Drupal runtime repo containing the dockerfiles and other config data
// to build the runtime images. After '@' a git ref can be specified (default: "master").
// Example: "https://gitlab.cern.ch/drupal/paas/drupal-runtime.git@s2i"
runtimeRepo := strings.Split(getenvOrDie("RUNTIME_REPO", log), "@")
ImageRecipesRepo = runtimeRepo[0]
if len(runtimeRepo) > 1 {
ImageRecipesRepoRef = runtimeRepo[1]
} else {
ImageRecipesRepoRef = "master"
}
ClusterName = getenvOrDie("CLUSTER_NAME", log)
ImageRecipesRepoDownload := strings.Trim(runtimeRepo[0], ".git") + "/repository/archive.tar?path=configuration&ref=" + ImageRecipesRepoRef
directoryName := downloadFile(ImageRecipesRepoDownload, "/tmp/repo.tar", log)
configPath := "/tmp/drupal-runtime/"
createConfigDirectory(configPath, log)
untar("/tmp/repo.tar", "/tmp/drupal-runtime", log)
renameConfigDirectory(directoryName, "/tmp/drupal-runtime", log)
}
// SetupWithManager adds a manager which watches the resources
func (r *DrupalSiteReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.initEnv()
return ctrl.NewControllerManagedBy(mgr).
For(&webservicesv1a1.DrupalSite{}).
Owns(&appsv1.Deployment{}).
......@@ -207,3 +238,130 @@ func setErrorCondition(drp *webservicesv1a1.DrupalSite, err reconcileError) (upd
Message: err.Error(),
})
}
// getenvOrDie checks for the given variable in the environment, if not exists
func getenvOrDie(name string, log logr.Logger) string {
e := os.Getenv(name)
if e == "" {
log.Info(name + ": missing environment variable (unset or empty string)")
os.Exit(1)
}
return e
}
// downloadFile downloads from the given URL and writes it to the given filename
func downloadFile(url string, fileName string, log logr.Logger) string {
resp, err := http.Get(url)
if err != nil || resp.StatusCode != 200 {
log.Error(err, fmt.Sprintf("fetching image recipes repo failed"))
os.Exit(1)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
log.Info("Received non 200 response code")
os.Exit(1)
}
directoryName := strings.TrimRight(strings.Trim(strings.Split(resp.Header["Content-Disposition"][0], "=")[1], "\""), ".tar")
//Create a empty file
file, err := os.Create(fileName)
if err != nil {
log.Error(err, fmt.Sprintf("Failed to create file"))
os.Exit(1)
}
defer file.Close()
//Write the bytes to the file
_, err = io.Copy(file, resp.Body)
if err != nil {
log.Error(err, fmt.Sprintf("Failed to write to a file"))
os.Exit(1)
}
log.Info(fmt.Sprintf("Downloaded the file %s", fileName))
return directoryName
}
// untar decompress the given tar file to the given target directory
func untar(tarball, target string, log logr.Logger) {
reader, err := os.Open(tarball)
if err != nil {
log.Error(err, fmt.Sprintf("Failed to open the tar file"))
os.Exit(1)
}
defer reader.Close()
tarReader := tar.NewReader(reader)
for {
header, err := tarReader.Next()
if err == io.EOF {
break
} else if err != nil {
log.Error(err, fmt.Sprintf("Failed to read the file"))
os.Exit(1)
}
path := filepath.Join(target, header.Name)
info := header.FileInfo()
if info.IsDir() {
if err = os.MkdirAll(path, info.Mode()); err != nil {
log.Error(err, fmt.Sprintf("Failed to create a directory"))
os.Exit(1)
}
continue
}
file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode())
if err != nil {
log.Error(err, fmt.Sprintf("Failed to create file"))
os.Exit(1)
}
defer file.Close()
_, err = io.Copy(file, tarReader)
if err != nil {
log.Error(err, fmt.Sprintf("Failed to write to a file"))
os.Exit(1)
}
}
}
// renameConfigDirectory reorganises the configuration files into the appropriate directories after decompressing them
func renameConfigDirectory(directoryName string, path string, log logr.Logger) {
moveFile("/tmp/drupal-runtime/"+directoryName+"/configuration/qos-critical", "/tmp/qos-critical", log)
moveFile("/tmp/drupal-runtime/"+directoryName+"/configuration/qos-eco", "/tmp/qos-eco", log)
moveFile("/tmp/drupal-runtime/"+directoryName+"/configuration/qos-standard", "/tmp/qos-standard", log)
removeFileIfExists("/tmp/drupal-runtime", log)
}
// createConfigDirectory creates the required directories to download the configurations
func createConfigDirectory(configPath string, log logr.Logger) {
removeFileIfExists("/tmp/qos-critical", log)
removeFileIfExists("/tmp/qos-eco", log)
removeFileIfExists("/tmp/qos-standard", log)
removeFileIfExists("/tmp/drupal-runtime", log)
err := os.MkdirAll("/tmp/drupal-runtime", 0755)
log.Info(fmt.Sprintf("Creating Directory %s", "/tmp/drupal-runtime"))
if err != nil {
log.Error(err, fmt.Sprintf("Failed to create the directory /tmp/drupal-runtime"))
os.Exit(1)
}
}
// removeFileIfExists checks if the given file/ directory exists. If it does, it removes it
func removeFileIfExists(path string, log logr.Logger) {
_, err := os.Stat(path)
if !os.IsNotExist(err) {
err = os.RemoveAll(path)
if err != nil {
log.Error(err, fmt.Sprintf("Failed to delete the directory %s", path))
os.Exit(1)
}
}
}
// moveFile checks if the given file/ directory exists. If it does, it removes it
func moveFile(from string, to string, log logr.Logger) {
err := os.Rename(from, to)
if err != nil {
log.Error(err, fmt.Sprintf("Failed to move the directory from %s to %s", from, to))
os.Exit(1)
}
}
This diff is collapsed.
......@@ -22,16 +22,22 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/client-go/kubernetes/scheme"
drupalwebservicesv1alpha1 "gitlab.cern.ch/drupal/paas/drupalsite-operator/api/v1alpha1"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
drupalwebservicesv1alpha1 "gitlab.cern.ch/drupal/paas/drupalsite-operator/api/v1alpha1"
// +kubebuilder:scaffold:imports
buildv1 "github.com/openshift/api/build/v1"
imagev1 "github.com/openshift/api/image/v1"
routev1 "github.com/openshift/api/route/v1"
appsv1 "k8s.io/api/apps/v1"
)
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
......@@ -49,28 +55,73 @@ func TestAPIs(t *testing.T) {
[]Reporter{printer.NewlineReporter{}})
}
var _ = BeforeSuite(func() {
var scheme = runtime.NewScheme()
var _ = BeforeSuite(func(done Done) {
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
customApiServerFlags := []string{
"--secure-port=6884",
}
apiServerFlags := append([]string(nil), envtest.DefaultKubeAPIServerFlags...)
apiServerFlags = append(apiServerFlags, customApiServerFlags...)
By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
CRDDirectoryPaths: []string{
filepath.Join("..", "config", "crd", "bases"),
filepath.Join("..", "testResources", "mock_crd"),
},
BinaryAssetsDirectory: filepath.Join("..", "testbin", "bin"),
ErrorIfCRDPathMissing: true,
KubeAPIServerFlags: apiServerFlags,
AttachControlPlaneOutput: true,
}
err := drupalwebservicesv1alpha1.AddToScheme(scheme)
Expect(err).NotTo(HaveOccurred())
// +kubebuilder:scaffold:scheme
cfg, err := testEnv.Start()
err = clientgoscheme.AddToScheme(scheme)
Expect(err).NotTo(HaveOccurred())
Expect(cfg).NotTo(BeNil())
err = drupalwebservicesv1alpha1.AddToScheme(scheme.Scheme)
err = buildv1.AddToScheme(scheme)
Expect(err).NotTo(HaveOccurred())
// +kubebuilder:scaffold:scheme
err = appsv1.AddToScheme(scheme)
Expect(err).NotTo(HaveOccurred())
err = routev1.AddToScheme(scheme)
Expect(err).NotTo(HaveOccurred())
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
err = imagev1.AddToScheme(scheme)
Expect(err).NotTo(HaveOccurred())
cfg, err := testEnv.Start()
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient).NotTo(BeNil())
Expect(cfg).NotTo(BeNil())
k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
Scheme: scheme,
})
Expect(err).ToNot(HaveOccurred())
err = (&DrupalSiteReconciler{
Client: k8sManager.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("DrupalSite"),
}).SetupWithManager(k8sManager)
Expect(err).ToNot(HaveOccurred())
go func() {
err = k8sManager.Start(ctrl.SetupSignalHandler())
Expect(err).ToNot(HaveOccurred())
}()
k8sClient = k8sManager.GetClient()
Expect(k8sClient).ToNot(BeNil())
}, 60)
close(done)
}, 100)
var _ = AfterSuite(func() {
By("tearing down the test environment")
......
......@@ -14,4 +14,4 @@ require (
k8s.io/client-go v0.19.2
k8s.io/utils v0.0.0-20200912215256-4140de9c8800
sigs.k8s.io/controller-runtime v0.7.0
)
)
This diff is collapsed.
......@@ -17,14 +17,8 @@ limitations under the License.
package main
import (
"archive/tar"
"flag"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
......@@ -77,7 +71,6 @@ func main() {
}
opts.BindFlags(flag.CommandLine)
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
initEnv()
flag.Parse()
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
......@@ -118,152 +111,3 @@ func main() {
os.Exit(1)
}
}
func initEnv() {
// <git_url>@<git_ref>
// Drupal runtime repo containing the dockerfiles and other config data
// to build the runtime images. After '@' a git ref can be specified (default: "master").
// Example: "https://gitlab.cern.ch/drupal/paas/drupal-runtime.git@s2i"
runtimeRepo := strings.Split(getenvOrDie("RUNTIME_REPO"), "@")
controllers.ImageRecipesRepo = runtimeRepo[0]
if len(runtimeRepo) > 1 {
controllers.ImageRecipesRepoRef = runtimeRepo[1]
} else {
controllers.ImageRecipesRepoRef = "master"
}
controllers.ClusterName = getenvOrDie("CLUSTER_NAME")
ImageRecipesRepoDownload := strings.Trim(runtimeRepo[0], ".git") + "/repository/archive.tar?path=configuration&ref=" + controllers.ImageRecipesRepoRef
directoryName := downloadFile(ImageRecipesRepoDownload, "/tmp/repo.tar")
configPath := "/tmp/drupal-runtime/"
createConfigDirectory(configPath)
untar("/tmp/repo.tar", "/tmp/drupal-runtime")
renameConfigDirectory(directoryName, "/tmp/drupal-runtime")
}
// getenvOrDie checks for the given variable in the environment, if not exists
func getenvOrDie(name string) string {
e := os.Getenv(name)
if e == "" {
setupLog.Info(name + ": missing environment variable (unset or empty string)")
os.Exit(1)
}
return e
}
// downloadFile downloads from the given URL and writes it to the given filename
func downloadFile(url string, fileName string) string {
resp, err := http.Get(url)
if err != nil || resp.StatusCode != 200 {
setupLog.Error(err, fmt.Sprintf("fetching image recipes repo failed"))
os.Exit(1)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
setupLog.Info("Received non 200 response code")
os.Exit(1)
}
directoryName := strings.TrimRight(strings.Trim(strings.Split(resp.Header["Content-Disposition"][0], "=")[1], "\""), ".tar")
//Create a empty file
file, err := os.Create(fileName)
if err != nil {
setupLog.Error(err, fmt.Sprintf("Failed to create file"))
os.Exit(1)
}
defer file.Close()
//Write the bytes to the file
_, err = io.Copy(file, resp.Body)
if err != nil {
setupLog.Error(err, fmt.Sprintf("Failed to write to a file"))
os.Exit(1)
}
setupLog.Info(fmt.Sprintf("Downloaded the file %s", fileName))
return directoryName
}
// untar decompress the given tar file to the given target directory
func untar(tarball, target string) {
reader, err := os.Open(tarball)
if err != nil {
setupLog.Error(err, fmt.Sprintf("Failed to open the tar file"))
os.Exit(1)
}
defer reader.Close()
tarReader := tar.NewReader(reader)
for {
header, err := tarReader.Next()
if err == io.EOF {
break
} else if err != nil {
setupLog.Error(err, fmt.Sprintf("Failed to read the file"))
os.Exit(1)
}
path := filepath.Join(target, header.Name)
info := header.FileInfo()
if info.IsDir() {
if err = os.MkdirAll(path, info.Mode()); err != nil {
setupLog.Error(err, fmt.Sprintf("Failed to create a directory"))
os.Exit(1)
}
continue
}
file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode())
if err != nil {
setupLog.Error(err, fmt.Sprintf("Failed to create file"))
os.Exit(1)
}
defer file.Close()
_, err = io.Copy(file, tarReader)
if err != nil {
setupLog.Error(err, fmt.Sprintf("Failed to write to a file"))
os.Exit(1)
}
}
}
// renameConfigDirectory reorganises the configuration files into the appropriate directories after decompressing them
func renameConfigDirectory(directoryName string, path string) {
moveFile("/tmp/drupal-runtime/"+directoryName+"/configuration/qos-critical", "/tmp/qos-critical")
moveFile("/tmp/drupal-runtime/"+directoryName+"/configuration/qos-eco", "/tmp/qos-eco")
moveFile("/tmp/drupal-runtime/"+directoryName+"/configuration/qos-standard", "/tmp/qos-standard")
removeFileIfExists("/tmp/drupal-runtime")
}
// createConfigDirectory creates the required directories to download the configurations
func createConfigDirectory(configPath string) {
removeFileIfExists("/tmp/qos-critical")
removeFileIfExists("/tmp/qos-eco")
removeFileIfExists("/tmp/qos-standard")
removeFileIfExists("/tmp/drupal-runtime")
err := os.MkdirAll("/tmp/drupal-runtime", 0755)
setupLog.Info(fmt.Sprintf("Creating Directory %s", "/tmp/drupal-runtime"))
if err != nil {
setupLog.Error(err, fmt.Sprintf("Failed to create the directory /tmp/drupal-runtime"))
os.Exit(1)
}
}
// removeFileIfExists checks if the given file/ directory exists. If it does, it removes it
func removeFileIfExists(path string) {
_, err := os.Stat(path)
if !os.IsNotExist(err) {
err = os.RemoveAll(path)
if err != nil {
setupLog.Error(err, fmt.Sprintf("Failed to delete the directory %s", path))
os.Exit(1)
}
}
}
// moveFile checks if the given file/ directory exists. If it does, it removes it
func moveFile(from string, to string) {
err := os.Rename(from, to)
if err != nil {
setupLog.Error(err, fmt.Sprintf("Failed to move the directory from %s to %s", from, to))
os.Exit(1)
}
}
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
# name must match the spec fields below, and be in the form: <plural>.<group>
name: buildconfigs.build.openshift.io
spec:
# group name to use for REST API: /apis/<group>/<version>
group: build.openshift.io
# list of versions supported by this CustomResourceDefinition
version: v1
# either Namespaced or Cluster
scope: Namespaced
subresources:
# enable spec/status
status: {}
names:
# plural name to be used in the URL: /apis/<group>/<version>/<plural>
plural: buildconfigs
# singular name to be used as an alias on the CLI and for display
singular: buildconfig
# kind is normally the CamelCased singular type. Your resource manifests use this.
kind: BuildConfig
\ No newline at end of file
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
# name must match the spec fields below, and be in the form: <plural>.<group>
name: imagestreams.image.openshift.io
spec:
# group name to use for REST API: /apis/<group>/<version>
group: image.openshift.io
# list of versions supported by this CustomResourceDefinition
version: v1
# either Namespaced or Cluster
scope: Namespaced
subresources:
# enable spec/status
status: {}
names:
# plural name to be used in the URL: /apis/<group>/<version>/<plural>
plural: imagestreams
# singular name to be used as an alias on the CLI and for display
singular: imagestream
# kind is normally the CamelCased singular type. Your resource manifests use this.
kind: ImageStream
\ No newline at end of file
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
# name must match the spec fields below, and be in the form: <plural>.<group>
name: routes.route.openshift.io
spec:
# group name to use for REST API: /apis/<group>/<version>
group: route.openshift.io
# list of versions supported by this CustomResourceDefinition
version: v1
# either Namespaced or Cluster
scope: Namespaced
subresources:
# enable spec/status
status: {}
names:
# plural name to be used in the URL: /apis/<group>/<version>/<plural>
plural: routes
# singular name to be used as an alias on the CLI and for display
singular: route
# kind is normally the CamelCased singular type. Your resource manifests use this.
kind: Route
additionalPrinterColumns:
- name: Host
type: string
JSONPath: .status.ingress[0].host
- name: Admitted
type: string
JSONPath: .status.ingress[0].conditions[?(@.type=="Admitted")].status
- name: Service
type: string
JSONPath: .spec.to.name
- name: TLS
type: string
JSONPath: .spec.tls.type
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment