Skip to content

Commit

Permalink
Add readme updates
Browse files Browse the repository at this point in the history
  • Loading branch information
sjakati98 committed Jul 15, 2019
1 parent 5129d73 commit 116880e
Show file tree
Hide file tree
Showing 180 changed files with 274 additions and 41,842 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ test/drone/
test/bitcoind/
test/tomcat/
temp/
test-operator/
test-operator/
helm2go-operator-sdk
104 changes: 77 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,96 @@
[![Build Status](https://travis-ci.org/redhat-nfvpe/service-assurance-poc.svg?branch=master)](https://travis-ci.org/redhat-nfvpe/helm2go-operator-sdk) [![Go Report Card](https://goreportcard.com/badge/github.com/redhat-nfvpe/helm2go-operator-sdk)](https://goreportcard.com/report/github.com/redhat-nfvpe/helm2go-operator-sdk)


![alt text](docs/design.png)

## Design
## Overview
---
This project is a small tool to produce Go Operators corresponding to Helm Charts in a reproducible and scalable way. Read more about the design in the [design doc](docs/Design.md).

### Render
Render handles the primary steps in the Helm to Go Kubernetes pathway. The main responsibility of this package is to render valid Helm charts. Additionally, the package can write the injected files to a specified temp directory.
[Helm](https://github.com/helm/helm) is a tool used for managing Kubernetes charts. Charts are packages of pre-configured Kubernetes resources. Helm allows for versioning and distribution of native Kubernetes applications.

### Convert
Convert handles the secondary steps in the Helm to Go Kubernetes pathway. The main responsibility of this package is the unmarshal the rendered YAML files and produce raw Kubernetes resources.
Go Operators are native Kubernetes applications used to deploy, upgrade, and manage other Kubernetes applications.

The file `pkg/convert/convert.go` contains the main logic to accomplish the conversion itself. `YAMLUnmarshalResources` receives an absolute path to a directory and simply unmarshals all resource files one at a time.

## Workflow
---
The tool provides the following workflow to develop operators in Go from corresponding Helm Charts:

1. Identify Helm Chart
* Tool supports local charts
* Tool supports external charts i.e. those hosted on external repositories
2. Specify the neccessary resource, and the API is generated adding Custom Resource Definitions (CRDs)
3. *Supported* Kubernetes Resources Controllers are automatically generated
4. User must write the reconciling logic for the controller using the [Operator-SDK](https://github.com/operator-framework/operator-sdk) and [controller-runtime](https://godoc.org/sigs.k8s.io/controller-runtime) APIs.
5. Use the [Operator-SDK](https://github.com/operator-framework/operator-sdk) CLI to build and generate the operator deployment manifests.

## Flags
The existing flags are as listed:
```
--api-version string Kubernetes apiVersion and has a format of $GROUP_NAME/$VERSION (e.g app.example.com/v1alpha1)
--cluster-scoped Operator cluster scoped or not
--helm-chart string Initialize helm operator with existing helm chart (<URL>, <repo>/<name>, or local path)
--helm-chart-ca-file string CA File For External Repo (Optional)
--helm-chart-cert-file string Cert File For External Repo (Optional)
--helm-chart-key-file string Key File For External Repo (Optional)
--helm-chart-version string Specific version of the helm chart (default is latest version)
--help help for convert
--kind string Kubernetes CustomResourceDefintion kind. (e.g AppService)
--password string Password for chart repo (Optional)
--username string Username for chart repo (Optional)
```

## How To Use
## Prerequisites
---
* [git](https://git-scm.com/downloads)
* [go](https://golang.org/dl/) version v1.12+
* [operator-sdk](https://github.com/operator-framework/operator-sdk) version v0.8+
* [dep](https://golang.github.io/dep/docs/installation.html) version v0.5.0+

To create an operator from an existing *local* helm chart:

## Quick Start
---
In the following example, we will create an nginx-operator using the existing [Bitnami Nginx](https://github.com/bitnami/charts/tree/master/bitnami/nginx) Helm Chart.

### Create, Build and Deploy an *nginx-operator* from Local Chart
```
go run main.go convert <OperatorName> --helm-chart=/path/to/chart --kind=Kind --api-version=apps.example.com/v1alpha1
# Create an nginx-operator that defines the Ngnix CR
$ export GO111MODULE=on
# Begin scaffolding process
$ helm2go-operator-sdk convert nginx-operator --helm-chart=/path/to/nginx --api-version=web.example.com/v1alpha1 --kind=Ngnix
# Enter operator directory
$ cd nginx-operator
# Build the operator
$ export GO111MODULE=off
$ operator-sdk build quay.io/example/image
$ docker push quay.io/example/image
# Deploy the Operator
# Update the operator manifest to use the built image name (if you are performing these steps on OSX, see note below)
$ sed -i 's|REPLACE_IMAGE|quay.io/example/app-operator|g' deploy/operator.yaml
# On OSX use:
$ sed -i "" 's|REPLACE_IMAGE|quay.io/example/app-operator|g' deploy/operator.yaml
# Setup Service Account
$ kubectl create -f deploy/service_account.yaml
# Setup RBAC
$ kubectl create -f deploy/role.yaml
$ kubectl create -f deploy/role_binding.yaml
# Setup the CRD
$ kubectl create -f deploy/crds/web_v1alpha1_nginx_crd.yaml
# Deploy the app-operator
$ kubectl create -f deploy/operator.yaml
# Create an AppService CR
# The default controller will watch for AppService objects and create a pod for each CR
$ kubectl create -f deploy/crds/app_v1alpha1_nginx_cr.yaml
# Verify that a pod is created
$ kubectl get pod -l app=example-nginx
NAME READY STATUS RESTARTS AGE
example-nginx-pod 1/1 Running 0 1m
```

To create an operator from an *external* helm chart:
## Supported Kubernetes Resources
---
The tool currently supports a very limited selection of Kubernetes resources, as listed here:
```
go run main.go convert <OperatorName> --helm-chart-repo=https://charts.example.io/ --helm-chart=example-chart --kind=Kind --api-version=apps.example.com/v1alpha1
Role
ClusterRole
RoleBinding
ClusterRoleBinding
ServiceAccount
Service
Deployment
```
If attempting to parse a Kubernetes resource other than the ones listed above the tool will prompt the user to either `continue` code generation without the unsupported resources or `stop` the code generation all together.


## Common Problems
---
If you are experiencing build errors: `go: error loading module requirements`, execute the following command `export GO111MODULES=off` within the operator folder.
10 changes: 5 additions & 5 deletions cmd/helm2go-operator-sdk/convert/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,27 +88,27 @@ func doHelmGoConversion() (*resourcecache.ResourceCache, error) {
// render the helm charts
f, err := render.InjectTemplateValues(c)
if err != nil {
return nil, err
return nil, fmt.Errorf("error injecting template values: %v", err)
}
// write the rendered charts to output directory
d, _ := os.Getwd()
temp, err := render.InjectedToTemp(f, d)
if err != nil {
return nil, err
return nil, fmt.Errorf("error injecting template values: %v", err)
}

to := filepath.Join(temp, chartName, "templates")

// perform version validation
// perform resource validation
validMap, err := load.PerformResourceValidation(to)
if err != nil {
return nil, err
return nil, fmt.Errorf("error performing resource validation: %v", err)
}

// convert the helm templates to go structures
rcache, err := load.YAMLUnmarshalResources(to, validMap, resourcecache.NewResourceCache())
if err != nil {
return nil, err
return nil, fmt.Errorf("error performing yaml unmarshaling: %v", err)
}

// clean up temp folder
Expand Down
16 changes: 8 additions & 8 deletions cmd/helm2go-operator-sdk/convert/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func NewConvertCmd() *cobra.Command {
newCmd.Flags().StringVar(&helmChartVersion, "helm-chart-version", "", "Specific version of the helm chart (default is latest version)")
newCmd.Flags().StringVar(&helmChartRepo, "helm-chart-repo", "", "Chart repository URL for the requested helm chart")
newCmd.Flags().StringVar(&username, "username", "", "Username for chart repo")
newCmd.Flags().StringVar(&username, "password", "", "Password for chart repo")
newCmd.Flags().StringVar(&password, "password", "", "Password for chart repo")
newCmd.Flags().StringVar(&helmChartCertFile, "helm-chart-cert-file", "", "Cert File For External Repo")
newCmd.Flags().StringVar(&helmChartKeyFile, "helm-chart-key-file", "", "Key File For External Repo")
newCmd.Flags().StringVar(&helmChartCAFile, "helm-chart-ca-file", "", "CA File For External Repo")
Expand Down Expand Up @@ -52,16 +52,16 @@ var (

func convertFunc(cmd *cobra.Command, args []string) error {
if err := parse(args); err != nil {
log.Error(err)
log.Error("error parsing arguments: ", err)
return err
}
if err := verifyFlags(); err != nil {
log.Error(err)
log.Error("error verifying flags: ", err)
return err
}

if err := verifyOperatorSDKVersion(); err != nil {
log.Error(err)
log.Error("error verifying operator-sdk version: ", err)
return err
}

Expand All @@ -70,26 +70,26 @@ func convertFunc(cmd *cobra.Command, args []string) error {
// load the spcecified helm chart
err := loadChart()
if err != nil {
log.Error(err)
log.Error("error loading chart: ", err)
return err
}

rcache, err := doHelmGoConversion()
if err != nil {
log.Error(err)
log.Error("error performing chart conversion: ", err)
return err
}

//create the operator-sdk scaffold
_, err = doGoScaffold()
if err != nil {
log.Error(err)
log.Error("error generating scaffolding: ", err)
return err
}

err = scaffoldOverwrite(outputDir, kind, apiVersion, rcache)
if err != nil {
log.Error(err)
log.Error("error overwritting scaffold: ", err)
return err
}

Expand Down
33 changes: 33 additions & 0 deletions docs/CLI-Reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# CLI Guide

**_Note:_** Binary has not yet been build.

```terminal
Usage:
go run main.go [command]
```

## convert
---
Scaffolds a Go Operator for a corresponding Helm Chart.

### Args
* `operator-name` - name of the new operator

### Flags
* `--helm-chart` - Name of the helm chart. If using an external repo, specify the name within the repo i.e. `nginx`. If using a local chart provide `path/to/chart`
* `--helm-chart-repo` - Specify external chart repo if necessary.
* `--helm-chart-version` - Specify external chart version if necessary.
* `--username` - Specify external repo username if necessary.
* `--password` - Specify external repo password if necessary.
* `--helm-chart-cert-file` - Specify Cert File for external repo if necessary.
* `--helm-chart-key-file` - Specify Key File for external repo if necessary.
* `--helm-chart-ca-file` - Specify CA File for external repo if necessary.
* `--api-version` - Kubernetes API Version and has a format of `<groupName>/<version>` i.e. `app.example.com/v1alpha1`
* `--kind` - Kubernetes Custom Resource Definition kind.
* `--cluster-scoped` - Operator cluster scoped or not.

### Example
```
$ go run main.go convert nginx-operator --helm-chart=path/to/chart --api-version=web.example.com/v1alpha1 --kind=Nginx
```
11 changes: 11 additions & 0 deletions docs/Design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
![alt text](design.png)

## Design

### Render
Render handles the primary steps in the Helm to Go Kubernetes pathway. The main responsibility of this package is to render valid Helm charts. Additionally, the package can write the injected files to a specified temp directory.

### Convert
Convert handles the secondary steps in the Helm to Go Kubernetes pathway. The main responsibility of this package is the unmarshal the rendered YAML files and produce raw Kubernetes resources.

The file `pkg/convert/convert.go` contains the main logic to accomplish the conversion itself. `YAMLUnmarshalResources` receives an absolute path to a directory and simply unmarshals all resource files one at a time.
10 changes: 7 additions & 3 deletions pkg/load/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,20 @@ func yamlUnmarshalSingleResource(rp string) (resourceConfig, error) {

func yamlUnmarshalSingleResourceFromBytes(fileBytes []byte) (resourceConfig, error) {

// instantiate decoder for decoding purposes
// // instantiate decoder for decoding purposes
// baseScheme := scheme.Scheme
// baseScheme.AddKnownTypes(networkingv1beta1.SchemeGroupVersion, &networkingv1beta1.Ingress{})
// // if baseScheme.IsVersionRegistered(networkingv1beta1.SchemeGroupVersion) {
// // return resourceConfig{}, fmt.Errorf("was not registered")
// // }
decode := scheme.Codecs.UniversalDeserializer().Decode

if isEmptyFile(fileBytes) {
return resourceConfig{}, fmt.Errorf("empty")
}

obj, _, err := decode([]byte(fileBytes), nil, nil)
if err != nil {
return resourceConfig{}, err
return resourceConfig{}, fmt.Errorf("error decoding bytes: %v", err)
}

// verify that the decoded resource kind is supported
Expand Down
4 changes: 2 additions & 2 deletions pkg/load/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func PerformResourceValidation(rp string) (*validatemap.ValidateMap, error) {
// collect all templated files in directory
files, err := ioutil.ReadDir(rp)
if err != nil {
return nil, err
return nil, fmt.Errorf("error reading files in directory %s: %v", rp, err)
}

var validMap validatemap.ValidateMap
Expand Down Expand Up @@ -55,7 +55,7 @@ func PerformResourceValidation(rp string) (*validatemap.ValidateMap, error) {
} else if e == "not yaml" || e == "empty" {
continue
} else {
return nil, err
return nil, fmt.Errorf("uncaught error: %v", err)
}
}
}
Expand Down
51 changes: 40 additions & 11 deletions pkg/templating/resources.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package templating

import (
"fmt"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"

"github.com/iancoleman/strcase"
Expand Down Expand Up @@ -80,6 +83,7 @@ var ResourceTitles = map[string]string{

var controllerKindImports = map[string]string{
"k8s.io/api/core/v1": "corev1",
"k8s.io/api/apps/v1": "appsv1",
"k8s.io/apimachinery/pkg/api/errors": "",
"k8s.io/apimachinery/pkg/apis/meta/v1": "metav1",
"k8s.io/apimachinery/pkg/runtime": "",
Expand Down Expand Up @@ -124,24 +128,49 @@ func kindToLowerCamel(kind string) string {
}

func getOwnerAPIVersion(apiVersion, kind string) string {
return filepath.Base(apiVersion) + kindToLowerCamel(kind)
return kindToLowerCamel(kind) + filepath.Base(apiVersion)
}

func getImportMap(outputDir, kind, apiVersion string) map[string]string {
controllerKindImports[getAppTypeImport(outputDir, apiVersion)] = getAppTypeImportAbbreviation(kind, apiVersion)

controllerKindImports[getAppTypeImport(outputDir, apiVersion)] = getOwnerAPIVersion(apiVersion, kind)
return controllerKindImports
}

func getAppTypeImport(outputDir, apiVersion string) string {
// append the correct path
// everything after source is in the correct path
sp := strings.Split(outputDir, "src/")
base := sp[len(sp)-1]
result := filepath.Join(base, "pkg", "apis", "apps", apiVersion)
return result
comps, err := getAPIVersionComponents(apiVersion)
if err != nil {
panic(err)
}

cwd, _ := os.Getwd()
cwd = filepath.Join(cwd, outputDir)

sp := strings.Split(cwd, "src/")
comps = append([]string{sp[len(sp)-1], "pkg", "apis"}, comps...)

return filepath.Join(comps...)

}

func getAppTypeImportAbbreviation(kind, apiVersion string) string {
result := apiVersion + kind
return result
func getAPIVersionComponents(input string) ([]string, error) {

var group string
var version string

// matches the input string and returns the groups
pattern := regexp.MustCompile(`(.*)\.(.*)\..*\/(.*)`)
matches := pattern.FindStringSubmatch(input)
if l := len(matches); l != 3+1 {
return []string{}, fmt.Errorf("expected four matches, received %d instead", l)
}
group = matches[1]
version = matches[3]

var result = []string{
group,
version,
}

return result, nil
}
Loading

0 comments on commit 116880e

Please sign in to comment.