Skip to content

Commit

Permalink
[FABRIC-7588] Added yaml exporter code into Zookeeper Operator (#52)
Browse files Browse the repository at this point in the history
* [FABRIC-7588] Added yaml exporter code into Zookeeper Operator
  • Loading branch information
vgupta-mickey authored and spiegela committed Feb 26, 2019
1 parent 85d0985 commit b823f94
Show file tree
Hide file tree
Showing 5 changed files with 308 additions and 1 deletion.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
SHELL=/bin/bash -o pipefail

PROJECT_NAME=zookeeper-operator
EXPORTER_NAME=zookeeper-exporter
APP_NAME=zookeeper
REPO=pravega/$(PROJECT_NAME)
APP_REPO=pravega/$(APP_NAME)
Expand All @@ -32,6 +33,9 @@ build-go:
CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build \
-ldflags "-X github.com/$(REPO)/pkg/version.Version=$(VERSION) -X github.com/$(REPO)/pkg/version.GitSHA=$(GIT_SHA)" \
-o bin/$(PROJECT_NAME) cmd/manager/main.go
CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build \
-ldflags "-X github.com/$(REPO)/pkg/version.Version=$(VERSION) -X github.com/$(REPO)/pkg/version.GitSHA=$(GIT_SHA)" \
-o bin/$(EXPORTER_NAME) cmd/exporter/main.go

build-image:
docker build --build-arg VERSION=$(VERSION) --build-arg GIT_SHA=$(GIT_SHA) -t $(REPO):$(VERSION) .
Expand Down
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,30 @@ On GKE, the following command must be run before installing the operator, replac

```
$ kubectl create clusterrolebinding your-user-cluster-admin-binding --clusterrole=cluster-admin --user=your.google.cloud.email@example.org
```
#### Zookeeper YAML Exporter
Zookeeper Exporter is a binary which is used to generate YAML file for all the secondary resources which Zookeeper Operator deploys to the Kubernetes Cluster. It takes ZookeeperCluster resource YAML file as input and generates bunch of secondary resources YAML files. The generated output look like the following:
>tree ZookeeperCluster/
ZookeeperCluster/
├── client
│   └── Service.yaml
├── config
│   └── ConfigMap.yaml
├── headless
│   └── Service.yaml
├── pdb
│   └── PodDisruptionBudget.yaml
└── zk
└── StatefulSet.yaml
#How to build Zookeeper Operator
When you build Operator, the Exporter is built along with it.
make build-go - will build both Operator as well as Exporter.
#How to use exporter
Just run zookeeper-exporter binary with -help option. It will guide you to input ZookeeperCluster YAML file. There are couple of more options to specify.
Example: ./zookeeper-exporter -i ./ZookeeperCluster.yaml -o .
91 changes: 91 additions & 0 deletions cmd/exporter/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* Copyright (c) 2018 Dell Inc., or its subsidiaries. All Rights Reserved.
*
* 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
*/

package main

import (
"flag"
"fmt"
"math/rand"
"os"
"runtime"
"strconv"
"time"

"k8s.io/apimachinery/pkg/types"

sdkVersion "github.com/operator-framework/operator-sdk/version"
"github.com/pravega/zookeeper-operator/pkg/controller/zookeepercluster"
"github.com/pravega/zookeeper-operator/pkg/version"
"github.com/pravega/zookeeper-operator/pkg/yamlexporter"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
)

var (
log = logf.Log.WithName("cmd")
versionFlag bool
)

func init() {
flag.BoolVar(&versionFlag, "version", false, "Show version and quit")
}

func printVersion() {
log.Info(fmt.Sprintf("zookeeper-operator Version: %v", version.Version))
log.Info(fmt.Sprintf("Git SHA: %s", version.GitSHA))
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("operator-sdk Version: %v", sdkVersion.Version))
}

func main() {
flags := flag.NewFlagSet("myFlagSet", flag.ExitOnError)
ifilePtr := flags.String("i", "./ZookeeperCluster.yaml", "Input YAML file")
odirPtr := flags.String("o", ".", "YAML output directory")

_ = flags.Parse(os.Args[1:])

log.Info(fmt.Sprintf("Input YAML file -i:%s", *ifilePtr))
log.Info(fmt.Sprintf("Output YAML Directory -o:%s", *odirPtr))

// Read input YAML file -- This is the ZookeeperCluster Resource YAML file
log.Info(fmt.Sprintf("Reading YAML file from the file:%s", *ifilePtr))
z, err := yamlexporter.ReadInputClusterYAMLFile(*ifilePtr)
if err != nil {
log.Error(err, "read input YAML file failed")
os.Exit(1)
}

// create base output directory and sub-directories named based on the deployment phase
log.Info(fmt.Sprintf("create base output dir:%s and phase based subdirs", *odirPtr))
err = yamlexporter.CreateYAMLOutputDir(*odirPtr)
if err != nil {
log.Error(err, "create output dir failed")
os.Exit(1)
}

// we need to provide our own UID for ECSCluster Resource, since the rest of the resources will reference UID of ECSCluster and it must be there.
rand.Seed(time.Now().UnixNano())
uid := strconv.FormatUint(rand.Uint64(), 10)
log.Info(fmt.Sprintf("UID of the ECSCluster Resource:%s\n", uid))
z.UID = types.UID(uid)

yamlexporter.YAMLOutputDir = *odirPtr

reconcilerZookeeper := zookeepercluster.YAMLExporterReconciler(z)

// Generate YAML files
err = reconcilerZookeeper.GenerateYAML(z)
if err != nil {
log.Error(err, "YAML file generation failed")
os.Exit(1)
}
}
96 changes: 96 additions & 0 deletions pkg/controller/zookeepercluster/zookeepercluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ import (
"fmt"
"time"

"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client/fake"

"github.com/pravega/zookeeper-operator/pkg/yamlexporter"

"k8s.io/apimachinery/pkg/labels"

"github.com/go-logr/logr"
Expand Down Expand Up @@ -366,3 +371,94 @@ func (r *ReconcileZookeeperCluster) reconcileClusterStatus(instance *zookeeperv1
"StatefulSet.Name", instance.Name)
return r.client.Status().Update(context.TODO(), instance)
}

// YAMLExporterReconciler returns a fake Reconciler which is being used for generating YAML files
func YAMLExporterReconciler(zookeepercluster *zookeeperv1beta1.ZookeeperCluster) *ReconcileZookeeperCluster {
var scheme = scheme.Scheme
scheme.AddKnownTypes(zookeeperv1beta1.SchemeGroupVersion, zookeepercluster)
return &ReconcileZookeeperCluster{
client: fake.NewFakeClient(zookeepercluster),
scheme: scheme,
}
}

// GenerateYAML generated secondary resource of ZookeeperCluster resources YAML files
func (r *ReconcileZookeeperCluster) GenerateYAML(inst *zookeeperv1beta1.ZookeeperCluster) error {
if inst.WithDefaults() {
fmt.Println("set default values")
}
for _, fun := range []reconcileFun{
r.yamlConfigMap,
r.yamlStatefulSet,
r.yamlClientService,
r.yamlHeadlessService,
r.yamlPodDisruptionBudget,
} {
if err := fun(inst); err != nil {
return err
}
}
return nil
}

// yamlStatefulSet will generates YAML file for StatefulSet
func (r *ReconcileZookeeperCluster) yamlStatefulSet(instance *zookeeperv1beta1.ZookeeperCluster) (err error) {
sts := zk.MakeStatefulSet(instance)
if err = controllerutil.SetControllerReference(instance, sts, r.scheme); err != nil {
return err
}
subdir, err := yamlexporter.CreateOutputSubDir(sts.OwnerReferences[0].Kind, sts.Labels["component"])
return yamlexporter.GenerateOutputYAMLFile(subdir, sts.Kind, sts)
}

// yamlClientService will generates YAML file for zookeeper client service
func (r *ReconcileZookeeperCluster) yamlClientService(instance *zookeeperv1beta1.ZookeeperCluster) (err error) {
svc := zk.MakeClientService(instance)
if err = controllerutil.SetControllerReference(instance, svc, r.scheme); err != nil {
return err
}
subdir, err := yamlexporter.CreateOutputSubDir(svc.OwnerReferences[0].Kind, "client")
if err != nil {
return err
}
return yamlexporter.GenerateOutputYAMLFile(subdir, svc.Kind, svc)
}

// yamlHeadlessService will generates YAML file for zookeeper headless service
func (r *ReconcileZookeeperCluster) yamlHeadlessService(instance *zookeeperv1beta1.ZookeeperCluster) (err error) {
svc := zk.MakeHeadlessService(instance)
if err = controllerutil.SetControllerReference(instance, svc, r.scheme); err != nil {
return err
}
subdir, err := yamlexporter.CreateOutputSubDir(svc.OwnerReferences[0].Kind, "headless")
if err != nil {
return err
}
return yamlexporter.GenerateOutputYAMLFile(subdir, svc.Kind, svc)
}

// yamlPodDisruptionBudget will generates YAML file for zookeeper PDB
func (r *ReconcileZookeeperCluster) yamlPodDisruptionBudget(instance *zookeeperv1beta1.ZookeeperCluster) (err error) {
pdb := zk.MakePodDisruptionBudget(instance)
if err = controllerutil.SetControllerReference(instance, pdb, r.scheme); err != nil {
return err
}
subdir, err := yamlexporter.CreateOutputSubDir(pdb.OwnerReferences[0].Kind, "pdb")
if err != nil {
return err
}
return yamlexporter.GenerateOutputYAMLFile(subdir, pdb.Kind, pdb)
}

// yamlConfigMap will generates YAML file for Zookeeper configmap
func (r *ReconcileZookeeperCluster) yamlConfigMap(instance *zookeeperv1beta1.ZookeeperCluster) (err error) {
cm := zk.MakeConfigMap(instance)
if err = controllerutil.SetControllerReference(instance, cm, r.scheme); err != nil {
return err
}
subdir, err := yamlexporter.CreateOutputSubDir(cm.OwnerReferences[0].Kind, "config")
if err != nil {
return err
}
return yamlexporter.GenerateOutputYAMLFile(subdir, cm.Kind, cm)
}
90 changes: 90 additions & 0 deletions pkg/yamlexporter/exportutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* Copyright (c) 2019 Dell Inc., or its subsidiaries. All Rights Reserved.
*
* 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
*/

package yamlexporter

import (
"bufio"
"io/ioutil"
"os"
"path/filepath"

"github.com/ghodss/yaml"
"github.com/pkg/errors"
"github.com/pravega/zookeeper-operator/pkg/apis/zookeeper/v1beta1"
)

// YAMLOutputDir where the zookeeper YAML resources will get generated
var YAMLOutputDir string

// CreateOutputSubDir creates a subdirectories where we want create the YAML file
func CreateOutputSubDir(clusterName string, compName string) (string, error) {
fpath := filepath.Join(clusterName, compName)
return fpath, createDirIfNotExist(fpath)
}

// GenerateOutputYAMLFile writes YAML output for a resource
func GenerateOutputYAMLFile(subdir string, depType string, data interface{}) error {
filename := filepath.Join(subdir, depType+"."+"yaml")
fileFd, err := os.Create(filename)
if err != nil {
return err
}
defer func() {
_ = fileFd.Close()
}()
yamlWriter := bufio.NewWriter(fileFd)
defer yamlWriter.Flush()
yamlData, err := yaml.Marshal(data)
if err != nil {
return err
}
n, err := yamlWriter.Write(yamlData)
if err != nil {
return errors.Wrapf(err, "write failed total bytes written:%d", n)
}
return nil
}

func createDirIfNotExist(dir string) error {
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0755)
if err != nil {
return err
}
}
return nil
}

// CreateYAMLOutputDir create output directory for YAML output
func CreateYAMLOutputDir(maindir string) error {
err := createDirIfNotExist(maindir)
if err != nil {
return err
}
return nil
}

// ReadInputClusterYAMLFile will read input YAML file and returns Go struct for ZookeeperCluster
func ReadInputClusterYAMLFile(inyamlfile string) (*v1beta1.ZookeeperCluster, error) {
if _, err := os.Stat(inyamlfile); os.IsNotExist(err) {
return nil, err
}
var z v1beta1.ZookeeperCluster
source, err := ioutil.ReadFile(inyamlfile)
if err != nil {
return nil, err
}
err = yaml.Unmarshal(source, &z)
if err != nil {
return nil, err
}
return &z, err
}

0 comments on commit b823f94

Please sign in to comment.