Skip to content

commands,pkg/scaffold,hack,pkg/helm: run and migrate command support for helm #897

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 15, 2019
Merged
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -2,8 +2,8 @@

### Added

- A new command [`operator-sdk migrate`](https://github.com/operator-framework/operator-sdk/blob/master/doc/sdk-cli-reference.md#migrate) which adds a main.go source file and any associated source files for an operator that is not of the "go" type. ([#887](https://github.com/operator-framework/operator-sdk/pull/887))
- A new command [`operator-sdk run ansible`](https://github.com/operator-framework/operator-sdk/blob/master/doc/sdk-cli-reference.md#ansible) which runs as an ansible operator process. This is intended to be used when running in a Pod inside a cluster. Developers wanting to run their operator locally should continue to use `up local`. ([#887](https://github.com/operator-framework/operator-sdk/pull/887))
- A new command [`operator-sdk migrate`](https://github.com/operator-framework/operator-sdk/blob/master/doc/sdk-cli-reference.md#migrate) which adds a main.go source file and any associated source files for an operator that is not of the "go" type. ([#887](https://github.com/operator-framework/operator-sdk/pull/887) and [#897](https://github.com/operator-framework/operator-sdk/pull/897))
- New commands [`operator-sdk run ansible`](https://github.com/operator-framework/operator-sdk/blob/master/doc/sdk-cli-reference.md#ansible) and [`operator-sdk run helm`](https://github.com/operator-framework/operator-sdk/blob/master/doc/sdk-cli-reference.md#helm) which run the SDK as ansible and helm operator processes, respectively. These are intended to be used when running in a Pod inside a cluster. Developers wanting to run their operator locally should continue to use `up local`. ([#887](https://github.com/operator-framework/operator-sdk/pull/887) and [#897](https://github.com/operator-framework/operator-sdk/pull/897))

### Changed

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -108,7 +108,7 @@ image/build: image/build/ansible image/build/helm
image/build/ansible: build/operator-sdk-dev-x86_64-linux-gnu
./hack/image/build-ansible-image.sh $(ANSIBLE_BASE_IMAGE):dev

image/build/helm:
image/build/helm: build/operator-sdk-dev-x86_64-linux-gnu
./hack/image/build-helm-image.sh $(HELM_BASE_IMAGE):dev

image/push: image/push/ansible image/push/helm
48 changes: 41 additions & 7 deletions commands/operator-sdk/cmd/migrate.go
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ import (
"github.com/operator-framework/operator-sdk/internal/util/projutil"
"github.com/operator-framework/operator-sdk/pkg/scaffold"
"github.com/operator-framework/operator-sdk/pkg/scaffold/ansible"
"github.com/operator-framework/operator-sdk/pkg/scaffold/helm"
"github.com/operator-framework/operator-sdk/pkg/scaffold/input"

log "github.com/sirupsen/logrus"
@@ -48,6 +49,8 @@ func migrateRun(cmd *cobra.Command, args []string) {
switch opType {
case projutil.OperatorTypeAnsible:
migrateAnsible()
case projutil.OperatorTypeHelm:
migrateHelm()
default:
log.Fatalf("Operator of type %s cannot be migrated.", opType)
}
@@ -76,13 +79,7 @@ func migrateAnsible() {
log.Fatalf("Error trying to stat playbook.yaml: (%v)", err)
}

dockerfilePath := filepath.Join(scaffold.BuildDir, scaffold.DockerfileFile)
newDockerfilePath := dockerfilePath + ".sdkold"
err = os.Rename(dockerfilePath, newDockerfilePath)
if err != nil {
log.Fatalf("Failed to rename Dockerfile: (%v)", err)
}
log.Infof("Renamed Dockerfile to %s and replaced with newer version. Compare the new Dockerfile to your old one and manually migrate any customizations", newDockerfilePath)
renameDockerfile()

s := &scaffold.Scaffold{}
err = s.Execute(cfg,
@@ -96,3 +93,40 @@ func migrateAnsible() {
log.Fatalf("Migrate scaffold failed: (%v)", err)
}
}

// migrateHelm runs the migration process for a helm-based operator
func migrateHelm() {
wd := projutil.MustGetwd()

cfg := &input.Config{
AbsProjectPath: wd,
ProjectName: filepath.Base(wd),
}

renameDockerfile()

s := &scaffold.Scaffold{}
err := s.Execute(cfg,
&helm.Main{},
&helm.GopkgToml{},
&helm.DockerfileHybrid{
Watches: true,
HelmCharts: true,
},
&helm.Entrypoint{},
&helm.UserSetup{},
)
if err != nil {
log.Fatalf("Migrate scaffold failed: (%v)", err)
}
}

func renameDockerfile() {
dockerfilePath := filepath.Join(scaffold.BuildDir, scaffold.DockerfileFile)
newDockerfilePath := dockerfilePath + ".sdkold"
err := os.Rename(dockerfilePath, newDockerfilePath)
if err != nil {
log.Fatalf("Failed to rename Dockerfile: (%v)", err)
}
log.Infof("Renamed Dockerfile to %s and replaced with newer version. Compare the new Dockerfile to your old one and manually migrate any customizations", newDockerfilePath)
}
1 change: 1 addition & 0 deletions commands/operator-sdk/cmd/run.go
Original file line number Diff line number Diff line change
@@ -32,5 +32,6 @@ should use "up local" instead.`,
}

runCmd.AddCommand(run.NewAnsibleCmd())
runCmd.AddCommand(run.NewHelmCmd())
return runCmd
}
3 changes: 1 addition & 2 deletions commands/operator-sdk/cmd/run/ansible.go
Original file line number Diff line number Diff line change
@@ -21,10 +21,9 @@ import (
"github.com/spf13/cobra"
)

var flags *aoflags.AnsibleOperatorFlags

// NewAnsibleCmd returns a command that will run an ansible operator
func NewAnsibleCmd() *cobra.Command {
var flags *aoflags.AnsibleOperatorFlags
newCmd := &cobra.Command{
Use: "ansible",
Short: "Runs as an ansible operator",
40 changes: 40 additions & 0 deletions commands/operator-sdk/cmd/run/helm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2019 The Operator-SDK Authors
//
// 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 run

import (
"github.com/operator-framework/operator-sdk/pkg/helm"
hoflags "github.com/operator-framework/operator-sdk/pkg/helm/flags"

"github.com/spf13/cobra"
)

// NewHelmCmd returns a command that will run a helm operator
func NewHelmCmd() *cobra.Command {
var flags *hoflags.HelmOperatorFlags
newCmd := &cobra.Command{
Use: "helm",
Short: "Runs as a helm operator",
Long: `Runs as a helm operator. This is intended to be used when running
in a Pod inside a cluster. Developers wanting to run their operator locally
should use "up local" instead.`,
Run: func(cmd *cobra.Command, args []string) {
helm.Run(flags)
},
}
flags = hoflags.AddTo(newCmd.Flags())

return newCmd
}
57 changes: 6 additions & 51 deletions commands/operator-sdk/cmd/up/local.go
Original file line number Diff line number Diff line change
@@ -25,26 +25,17 @@ import (
"strings"
"syscall"

"sigs.k8s.io/controller-runtime/pkg/runtime/signals"

"github.com/operator-framework/operator-sdk/internal/util/projutil"
"github.com/operator-framework/operator-sdk/pkg/ansible"
aoflags "github.com/operator-framework/operator-sdk/pkg/ansible/flags"
"github.com/operator-framework/operator-sdk/pkg/helm/client"
"github.com/operator-framework/operator-sdk/pkg/helm/controller"
"github.com/operator-framework/operator-sdk/pkg/helm"
hoflags "github.com/operator-framework/operator-sdk/pkg/helm/flags"
"github.com/operator-framework/operator-sdk/pkg/helm/release"
"github.com/operator-framework/operator-sdk/pkg/k8sutil"
"github.com/operator-framework/operator-sdk/pkg/scaffold"
sdkVersion "github.com/operator-framework/operator-sdk/version"
"k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/manager"
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
)

// NewLocalCmd - up local command to run an operator loccally
@@ -172,50 +163,14 @@ func upLocalHelm() {
log.Fatalf("Failed to set %s environment variable: (%v)", k8sutil.KubeConfigEnvVar, err)
}

logf.SetLogger(logf.ZapLogger(false))

printVersion()

cfg, err := config.GetConfig()
if err != nil {
log.Fatal(err)
}

mgr, err := manager.New(cfg, manager.Options{Namespace: namespace})
if err != nil {
log.Fatal(err)
}

// Create Tiller's storage backend and kubernetes client
storageBackend := storage.Init(driver.NewMemory())
tillerKubeClient, err := client.NewFromManager(mgr)
if err != nil {
log.Fatal(err)
}

factories, err := release.NewManagerFactoriesFromFile(storageBackend, tillerKubeClient, helmOperatorFlags.WatchesFile)
if err != nil {
log.Fatal(err)
}

for gvk, factory := range factories {
// Register the controller with the factory.
err := controller.Add(mgr, controller.WatchOptions{
Namespace: namespace,
GVK: gvk,
ManagerFactory: factory,
ReconcilePeriod: helmOperatorFlags.ReconcilePeriod,
WatchDependentResources: true,
})
if err != nil {
log.Fatal(err)
// Set the kubeconfig that the manager will be able to grab
if namespace != "" {
if err := os.Setenv(k8sutil.WatchNamespaceEnvVar, namespace); err != nil {
log.Fatalf("Failed to set %s environment variable: (%v)", k8sutil.WatchNamespaceEnvVar, err)
}
}

// Start the Cmd
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
log.Fatal(err)
}
helm.Run(helmOperatorFlags)
}

func printVersion() {
6 changes: 5 additions & 1 deletion doc/dev/release.md
Original file line number Diff line number Diff line change
@@ -171,13 +171,15 @@ Create a new branch to push release commits:
$ git checkout -b release-v1.3.0
```

Commit changes to the following four files:
Commit changes to the following six files:
* `version/version.go`: update `Version` to `v1.3.0`.
* `pkg/scaffold/gopkgtoml.go`, under the `[[constraint]]` for `github.com/operator-framework/operator-sdk`:
* Comment out `branch = "master"`
* Un-comment `version = "v1.2.0"`
* Change `v1.2.0` to `v1.3.0`
* `pkg/scaffold/gopkgtoml_test.go`: same as for `pkg/scaffold/gopkgtoml.go`.
* `pkg/scaffold/ansible/gopkgtoml.go`: same as for `pkg/scaffold/gopkgtoml.go`.
* `pkg/scaffold/helm/gopkgtoml.go`: same as for `pkg/scaffold/gopkgtoml.go`.
* `CHANGELOG.md`: update the `## Unreleased` header to `## v1.3.0`.

Create a new PR for `release-v1.3.0`.
@@ -216,6 +218,8 @@ Check out a new branch from master (or use your `release-v1.3.0`) and commit the
* Comment out `version = "v1.3.0"`
* Un-comment `branch = "master"`
* `pkg/scaffold/gopkgtoml_test.go`: same as for `pkg/scaffold/gopkgtoml.go`.
* `pkg/scaffold/ansible/gopkgtoml.go`: same as for `pkg/scaffold/gopkgtoml.go`.
* `pkg/scaffold/helm/gopkgtoml.go`: same as for `pkg/scaffold/gopkgtoml.go`.
* `CHANGELOG.md`: add the following as a new set of headers above `## v1.3.0`:
```
## Unreleased
24 changes: 15 additions & 9 deletions doc/dev/testing/travis-build.md
Original file line number Diff line number Diff line change
@@ -69,7 +69,7 @@ The Go, Ansible, and Helm tests then differ in what tests they run.
### Ansible tests

1. Run [ansible e2e tests][ansible-e2e].
1. Create base ansible operator image by running [`hack/image/scaffold-ansible-image.go`][ansible-base].
1. Create base ansible operator project by running [`hack/image/ansible/scaffold-ansible-image.go`][ansible-base].
2. Build base ansible operator image.
3. Create and configure a new ansible type memcached-operator.
4. Create cluster resources.
@@ -86,12 +86,18 @@ The Go, Ansible, and Helm tests then differ in what tests they run.
### Helm Tests

1. Run [helm e2e tests][helm-e2e].
1. Build base helm operator image from [`test/helm-operator`][helm-base].
2. Create and configure a new helm type nginx-operator.
3. Create cluster resources.
4. Wait for operator to be ready.
5. Create nginx CR and wait for it to be ready.
6. Delete nginx CR and verify that finalizer (which writes a message in the operator logs) ran.
1. Create base helm operator project by running [`hack/image/helm/scaffold-helm-image.go`][helm-base].
2. Build base helm operator image.
3. Create and configure a new helm type nginx-operator.
4. Create cluster resources.
5. Wait for operator to be ready.
6. Create nginx CR and wait for it to be ready.
7. Scale up the dependent deployment and verify the operator reconciles it back down.
8. Scale up the CR and verify the dependent deployment scales up accordingly.
9. Delete nginx CR and verify that finalizer (which writes a message in the operator logs) ran.
10. Run `operator-sdk migrate` to add go source to the operator.
11. Run `operator-sdk build` to compile the new binary and build a new image.
12. Re-run steps 4-9 to test the migrated operator.

**NOTE**: All created resources, including the namespace, are deleted using a bash trap when the test finishes

@@ -110,8 +116,8 @@ The markdown test does not create a new cluster and runs in a barebones travis V
[go-e2e]: ../../../hack/tests/e2e-go.sh
[tls-tests]: ../../../test/e2e/tls_util_test.go
[ansible-e2e]: ../../../hack/tests/e2e-ansible.sh
[ansible-base]: ../../../hack/image/scaffold-ansible-image.go
[ansible-base]: ../../../hack/image/ansible/scaffold-ansible-image.go
[helm-e2e]: ../../../hack/tests/e2e-helm.sh
[helm-base]: ../../../test/helm-operator
[helm-base]: ../../../hack/image/helm/scaffold-helm-image.go
[marker-github]: https://github.com/crawford/marker
[marker-local]: ../../../hack/ci/marker
17 changes: 17 additions & 0 deletions doc/sdk-cli-reference.md
Original file line number Diff line number Diff line change
@@ -300,6 +300,23 @@ should use `up local` instead.
$ operator-sdk run ansible --watches-file=/opt/ansible/watches.yaml --reconcile-period=30s
```

### helm

Runs as a helm operator process. This is intended to be used when running
in a Pod inside a cluster. Developers wanting to run their operator locally
should use `up local` instead.

#### Flags

* `--reconcile-period` string - Default reconcile period for controllers (default 1m0s)
* `--watches-file` string - Path to the watches file to use (default "./watches.yaml")

#### Example

```bash
$ operator-sdk run helm --watches-file=/opt/helm/watches.yaml --reconcile-period=30s
```

## test

### Available Commands
File renamed without changes.
2 changes: 1 addition & 1 deletion hack/image/build-ansible-image.sh
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ mkdir -p "$BASEIMAGEDIR"

# build operator binary and base image
pushd "$BASEIMAGEDIR"
go run "$ROOTDIR/hack/image/scaffold-ansible-image.go"
go run "$ROOTDIR/hack/image/ansible/scaffold-ansible-image.go"

mkdir -p build/_output/bin/
cp $ROOTDIR/build/operator-sdk-dev-x86_64-linux-gnu build/_output/bin/ansible-operator
17 changes: 14 additions & 3 deletions hack/image/build-helm-image.sh
Original file line number Diff line number Diff line change
@@ -2,8 +2,19 @@

set -eux

source hack/lib/test_lib.sh

ROOTDIR="$(pwd)"
GOTMP="$(mktemp -d -p $GOPATH/src)"
trap_add 'rm -rf $GOTMP' EXIT
BASEIMAGEDIR="$GOTMP/helm-operator"
mkdir -p "$BASEIMAGEDIR"

# build operator binary and base image
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o test/helm-operator/helm-operator test/helm-operator/cmd/helm-operator/main.go
pushd test/helm-operator
docker build -t "$1" .
pushd "$BASEIMAGEDIR"
go run "$ROOTDIR/hack/image/helm/scaffold-helm-image.go"

mkdir -p build/_output/bin/
cp $ROOTDIR/build/operator-sdk-dev-x86_64-linux-gnu build/_output/bin/helm-operator
operator-sdk build $1
popd
45 changes: 45 additions & 0 deletions hack/image/helm/scaffold-helm-image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2019 The Operator-SDK Authors
//
// 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 main

import (
"log"

"github.com/operator-framework/operator-sdk/internal/util/projutil"
"github.com/operator-framework/operator-sdk/pkg/scaffold"
"github.com/operator-framework/operator-sdk/pkg/scaffold/helm"
"github.com/operator-framework/operator-sdk/pkg/scaffold/input"
)

// main renders scaffolds that are required to build the helm operator base
// image. It is intended for release engineering use only. After running this,
// you can place a binary in `build/_output/bin/helm-operator` and then run
// `operator-sdk build`.
func main() {
cfg := &input.Config{
AbsProjectPath: projutil.MustGetwd(),
ProjectName: "helm-operator",
}

s := &scaffold.Scaffold{}
err := s.Execute(cfg,
&helm.DockerfileHybrid{},
&helm.Entrypoint{},
&helm.UserSetup{},
)
if err != nil {
log.Fatalf("add scaffold failed: (%v)", err)
}
}
175 changes: 105 additions & 70 deletions hack/tests/e2e-helm.sh
Original file line number Diff line number Diff line change
@@ -2,9 +2,85 @@

source hack/lib/test_lib.sh

DEST_IMAGE="quay.io/example/nginx-operator:v0.0.2"
set -eux

set -ex
DEST_IMAGE="quay.io/example/nginx-operator:v0.0.2"
ROOTDIR="$(pwd)"
GOTMP="$(mktemp -d -p $GOPATH/src)"
trap_add 'rm -rf $GOTMP' EXIT

deploy_operator() {
kubectl create -f "$OPERATORDIR/deploy/service_account.yaml"
kubectl create -f "$OPERATORDIR/deploy/role.yaml"
kubectl create -f "$OPERATORDIR/deploy/role_binding.yaml"
kubectl create -f "$OPERATORDIR/deploy/crds/helm_v1alpha1_nginx_crd.yaml"
kubectl create -f "$OPERATORDIR/deploy/operator.yaml"
}

remove_operator() {
kubectl delete --ignore-not-found=true -f "$OPERATORDIR/deploy/service_account.yaml"
kubectl delete --ignore-not-found=true -f "$OPERATORDIR/deploy/role.yaml"
kubectl delete --ignore-not-found=true -f "$OPERATORDIR/deploy/role_binding.yaml"
kubectl delete --ignore-not-found=true -f "$OPERATORDIR/deploy/crds/helm_v1alpha1_nginx_crd.yaml"
kubectl delete --ignore-not-found=true -f "$OPERATORDIR/deploy/operator.yaml"
}

test_operator() {
# wait for operator pod to run
if ! timeout 1m kubectl rollout status deployment/nginx-operator;
then
kubectl logs deployment/nginx-operator
exit 1
fi

# create CR
kubectl create -f deploy/crds/helm_v1alpha1_nginx_cr.yaml
trap_add 'kubectl delete --ignore-not-found -f ${OPERATORDIR}/deploy/crds/helm_v1alpha1_nginx_cr.yaml' EXIT
if ! timeout 1m bash -c -- 'until kubectl get nginxes.helm.example.com example-nginx -o jsonpath="{..status.conditions[1].release.info.status.code}" | grep 1; do sleep 1; done';
then
kubectl logs deployment/nginx-operator
exit 1
fi

release_name=$(kubectl get nginxes.helm.example.com example-nginx -o jsonpath="{..status.conditions[1].release.name}")
nginx_deployment=$(kubectl get deployment -l "app.kubernetes.io/instance=${release_name}" -o jsonpath="{..metadata.name}")

if ! timeout 1m kubectl rollout status deployment/${nginx_deployment};
then
kubectl describe pods -l "app.kubernetes.io/instance=${release_name}"
kubectl describe deployments ${nginx_deployment}
kubectl logs deployment/${nginx_deployment}
exit 1
fi

nginx_service=$(kubectl get service -l "app.kubernetes.io/instance=${release_name}" -o jsonpath="{..metadata.name}")
kubectl get service ${nginx_service}

# scale deployment replicas to 2 and verify the
# deployment automatically scales back down to 1.
kubectl scale deployment/${nginx_deployment} --replicas=2
if ! timeout 1m test $(kubectl get deployment/${nginx_deployment} -o jsonpath="{..spec.replicas}") -eq 1;
then
kubectl describe pods -l "app.kubernetes.io/instance=${release_name}"
kubectl describe deployments ${nginx_deployment}
kubectl logs deployment/${nginx_deployment}
exit 1
fi

# update CR to replicaCount=2 and verify the deployment
# automatically scales up to 2 replicas.
kubectl patch nginxes.helm.example.com example-nginx -p '[{"op":"replace","path":"/spec/replicaCount","value":2}]' --type=json
if ! timeout 1m test $(kubectl get deployment/${nginx_deployment} -o jsonpath="{..spec.replicas}") -eq 2;
then
kubectl describe pods -l "app.kubernetes.io/instance=${release_name}"
kubectl describe deployments ${nginx_deployment}
kubectl logs deployment/${nginx_deployment}
exit 1
fi

kubectl delete -f deploy/crds/helm_v1alpha1_nginx_cr.yaml --wait=true
kubectl logs deployment/nginx-operator | grep "Uninstalled release" | grep "${release_name}"
}

# if on openshift switch to the "default" namespace
# and allow containers to run as root (necessary for
@@ -15,93 +91,52 @@ then
oc adm policy add-scc-to-user anyuid -z default
fi

# Make a test directory for Helm tests so we avoid using default GOPATH.
# Save test directory so we can delete it on exit.
HELM_TEST_DIR="$(mktemp -d)"
trap_add 'rm -rf $HELM_TEST_DIR' EXIT
cp -a test/helm-* "$HELM_TEST_DIR"
pushd "$HELM_TEST_DIR"

# Helm tests should not run in a Golang environment.
unset GOPATH GOROOT

# create and build the operator
pushd "$GOTMP"
operator-sdk new nginx-operator --api-version=helm.example.com/v1alpha1 --kind=Nginx --type=helm

pushd nginx-operator
sed -i 's|\(FROM quay.io/operator-framework/helm-operator\)\(:.*\)\?|\1:dev|g' build/Dockerfile

operator-sdk build "$DEST_IMAGE"
sed -i "s|REPLACE_IMAGE|$DEST_IMAGE|g" deploy/operator.yaml
sed -i 's|Always|Never|g' deploy/operator.yaml
sed -i 's|size: 3|replicaCount: 1|g' deploy/crds/helm_v1alpha1_nginx_cr.yaml

DIR2="$(pwd)"
# deploy the operator
kubectl create -f deploy/service_account.yaml
trap_add 'kubectl delete -f ${DIR2}/deploy/service_account.yaml' EXIT
kubectl create -f deploy/role.yaml
trap_add 'kubectl delete -f ${DIR2}/deploy/role.yaml' EXIT
kubectl create -f deploy/role_binding.yaml
trap_add 'kubectl delete -f ${DIR2}/deploy/role_binding.yaml' EXIT
kubectl create -f deploy/crds/helm_v1alpha1_nginx_crd.yaml
trap_add 'kubectl delete -f ${DIR2}/deploy/crds/helm_v1alpha1_nginx_crd.yaml' EXIT
kubectl create -f deploy/operator.yaml
trap_add 'kubectl delete -f ${DIR2}/deploy/operator.yaml' EXIT

# wait for operator pod to run
if ! timeout 1m kubectl rollout status deployment/nginx-operator;
then
kubectl logs deployment/nginx-operator
exit 1
fi

# create CR
kubectl create -f deploy/crds/helm_v1alpha1_nginx_cr.yaml
trap_add 'kubectl delete --ignore-not-found -f ${DIR2}/deploy/crds/helm_v1alpha1_nginx_cr.yaml' EXIT
if ! timeout 1m bash -c -- 'until kubectl get nginxes.helm.example.com example-nginx -o jsonpath="{..status.conditions[1].release.info.status.code}" | grep 1; do sleep 1; done';
then
kubectl logs deployment/nginx-operator
exit 1
fi
OPERATORDIR="$(pwd)"

release_name=$(kubectl get nginxes.helm.example.com example-nginx -o jsonpath="{..status.conditions[1].release.name}")
nginx_deployment=$(kubectl get deployment -l "app.kubernetes.io/instance=${release_name}" -o jsonpath="{..metadata.name}")
deploy_operator
trap_add 'remove_operator' EXIT
test_operator
remove_operator

if ! timeout 1m kubectl rollout status deployment/${nginx_deployment};
then
kubectl describe pods -l "app.kubernetes.io/instance=${release_name}"
kubectl describe deployments ${nginx_deployment}
kubectl logs deployment/${nginx_deployment}
exit 1
fi
echo "###"
echo "### Base image testing passed"
echo "### Now testing migrate to hybrid operator"
echo "###"

nginx_service=$(kubectl get service -l "app.kubernetes.io/instance=${release_name}" -o jsonpath="{..metadata.name}")
kubectl get service ${nginx_service}
operator-sdk migrate

# scale deployment replicas to 2 and verify the
# deployment automatically scales back down to 1.
kubectl scale deployment/${nginx_deployment} --replicas=2
if ! timeout 1m test $(kubectl get deployment/${nginx_deployment} -o jsonpath="{..spec.replicas}") -eq 1;
if [[ ! -e build/Dockerfile.sdkold ]];
then
kubectl describe pods -l "app.kubernetes.io/instance=${release_name}"
kubectl describe deployments ${nginx_deployment}
kubectl logs deployment/${nginx_deployment}
echo FAIL the old Dockerfile should have been renamed to Dockerfile.sdkold
exit 1
fi

# update CR to replicaCount=2 and verify the deployment
# automatically scales up to 2 replicas.
kubectl patch nginxes.helm.example.com example-nginx -p '[{"op":"replace","path":"/spec/replicaCount","value":2}]' --type=json
if ! timeout 1m test $(kubectl get deployment/${nginx_deployment} -o jsonpath="{..spec.replicas}") -eq 2;
then
kubectl describe pods -l "app.kubernetes.io/instance=${release_name}"
kubectl describe deployments ${nginx_deployment}
kubectl logs deployment/${nginx_deployment}
exit 1
fi
# We can't reliably run `dep ensure` because when there are changes to
# operator-sdk itself, and those changes are not merged upstream, we hit this
# bug: https://github.com/golang/dep/issues/1747
# Instead, this re-uses operator-sdk's own vendor directory.
cp -a "$ROOTDIR"/vendor ./
mkdir -p vendor/github.com/operator-framework/operator-sdk/
# We cannot just use operator-sdk from $GOPATH because compilation tries to use
# its vendor directory, which can conflict with the local one.
cp -a "$ROOTDIR"/{internal,pkg,version,LICENSE} vendor/github.com/operator-framework/operator-sdk/

operator-sdk build "$DEST_IMAGE"

kubectl delete -f deploy/crds/helm_v1alpha1_nginx_cr.yaml --wait=true
kubectl logs deployment/nginx-operator | grep "Uninstalled release" | grep "${release_name}"
deploy_operator
test_operator

popd
popd
28 changes: 11 additions & 17 deletions test/helm-operator/cmd/helm-operator/main.go → pkg/helm/run.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2018 The Operator-SDK Authors
// Copyright 2019 The Operator-SDK Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package main
package helm

import (
"fmt"
@@ -25,7 +25,6 @@ import (
"github.com/operator-framework/operator-sdk/pkg/helm/release"
"github.com/operator-framework/operator-sdk/pkg/k8sutil"
sdkVersion "github.com/operator-framework/operator-sdk/version"
"github.com/spf13/pflag"

"k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver"
@@ -43,18 +42,17 @@ func printVersion() {
log.Info(fmt.Sprintf("Version of operator-sdk: %v", sdkVersion.Version))
}

func main() {
hflags := hoflags.AddTo(pflag.CommandLine)
pflag.Parse()

// Run runs the helm operator
func Run(flags *hoflags.HelmOperatorFlags) {
logf.SetLogger(logf.ZapLogger(false))

printVersion()

namespace, err := k8sutil.GetWatchNamespace()
if err != nil {
log.Error(err, "Failed to get watch namespace")
os.Exit(1)
namespace, found := os.LookupEnv(k8sutil.WatchNamespaceEnvVar)
if found {
log.Info("Watching single namespace", "namespace", namespace)
} else {
log.Info(k8sutil.WatchNamespaceEnvVar + " environment variable not set, watching all namespaces")
}

cfg, err := config.GetConfig()
@@ -69,8 +67,6 @@ func main() {
os.Exit(1)
}

log.Info("Registering Components.")

// Create Tiller's storage backend and kubernetes client
storageBackend := storage.Init(driver.NewMemory())
tillerKubeClient, err := client.NewFromManager(mgr)
@@ -79,7 +75,7 @@ func main() {
os.Exit(1)
}

factories, err := release.NewManagerFactoriesFromFile(storageBackend, tillerKubeClient, hflags.WatchesFile)
factories, err := release.NewManagerFactoriesFromFile(storageBackend, tillerKubeClient, flags.WatchesFile)
if err != nil {
log.Error(err, "")
os.Exit(1)
@@ -91,7 +87,7 @@ func main() {
Namespace: namespace,
GVK: gvk,
ManagerFactory: factory,
ReconcilePeriod: hflags.ReconcilePeriod,
ReconcilePeriod: flags.ReconcilePeriod,
WatchDependentResources: true,
})
if err != nil {
@@ -100,8 +96,6 @@ func main() {
}
}

log.Info("Starting the Cmd.")

// Start the Cmd
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
log.Error(err, "Manager exited non-zero")
64 changes: 64 additions & 0 deletions pkg/scaffold/helm/dockerfilehybrid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright 2019 The Operator-SDK Authors
//
// 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 helm

import (
"path/filepath"

"github.com/operator-framework/operator-sdk/pkg/scaffold"
"github.com/operator-framework/operator-sdk/pkg/scaffold/input"
)

//DockerfileHybrid - Dockerfile for a hybrid operator
type DockerfileHybrid struct {
input.Input

// HelmCharts - if true, include a COPY statement for the helm-charts directory
HelmCharts bool

// Watches - if true, include a COPY statement for watches.yaml
Watches bool
}

// GetInput - gets the input
func (d *DockerfileHybrid) GetInput() (input.Input, error) {
if d.Path == "" {
d.Path = filepath.Join(scaffold.BuildDir, scaffold.DockerfileFile)
}
d.TemplateBody = dockerFileHybridHelmTmpl
return d.Input, nil
}

const dockerFileHybridHelmTmpl = `FROM alpine:3.6
ENV OPERATOR=/usr/local/bin/helm-operator \
USER_UID=1001 \
USER_NAME=helm \
HOME=/opt/helm
# install operator binary
COPY build/_output/bin/{{.ProjectName}} ${OPERATOR}
COPY bin /usr/local/bin
RUN /usr/local/bin/user_setup
{{- if .HelmCharts }}
COPY helm-charts/ ${HOME}/helm-charts/{{ end }}
{{- if .Watches }}
COPY watches.yaml ${HOME}/watches.yaml{{ end }}
ENTRYPOINT ["/usr/local/bin/entrypoint"]
USER ${USER_UID}`
49 changes: 49 additions & 0 deletions pkg/scaffold/helm/entrypoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2019 The Operator-SDK Authors
//
// 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 helm

import (
"path/filepath"

"github.com/operator-framework/operator-sdk/pkg/scaffold/input"
)

// Entrypoint - entrypoint script
type Entrypoint struct {
input.Input
}

func (e *Entrypoint) GetInput() (input.Input, error) {
if e.Path == "" {
e.Path = filepath.Join("bin", "entrypoint")
}
e.TemplateBody = entrypointTmpl
e.IsExec = true
return e.Input, nil
}

const entrypointTmpl = `#!/bin/sh -e
# This is documented here:
# https://docs.openshift.com/container-platform/3.11/creating_images/guidelines.html#openshift-specific-guidelines
if ! whoami &>/dev/null; then
if [ -w /etc/passwd ]; then
echo "${USER_NAME:-helm}:x:$(id -u):$(id -g):${USER_NAME:-helm} user:${HOME}:/sbin/nologin" >> /etc/passwd
fi
fi
exec ${OPERATOR} run helm --watches-file=/opt/helm/watches.yaml $@
`
44 changes: 44 additions & 0 deletions pkg/scaffold/helm/gopkgtoml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2019 The Operator-SDK Authors
//
// 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 helm

import (
"github.com/operator-framework/operator-sdk/pkg/scaffold"
"github.com/operator-framework/operator-sdk/pkg/scaffold/input"
)

// GopkgToml - the Gopkg.toml file for a hybrid operator
type GopkgToml struct {
input.Input
}

func (s *GopkgToml) GetInput() (input.Input, error) {
if s.Path == "" {
s.Path = scaffold.GopkgTomlFile
}
s.TemplateBody = gopkgTomlTmpl
return s.Input, nil
}

const gopkgTomlTmpl = `[[constraint]]
name = "github.com/operator-framework/operator-sdk"
# The version rule is used for a specific release and the master branch for in between releases.
branch = "master" #osdk_branch_annotation
# version = "=v0.3.0" #osdk_version_annotation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just remembered that we need to update this scaffold for each release as well now.

@joelanford Can you please update the release guide section for the version update as well.

We need to bump both pkg/scaffold/ansible/gopkgtoml.go and this file pkg/scaffold/helm/gopkgtoml.go

@estroz We just need to add checks for these two files in the release.sh script to ensure the versions are set correctly at release time as well right? Similar to the existing checks:
https://github.com/operator-framework/operator-sdk/blob/master/release.sh#L38-L41

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@estroz scratch that. I just saw @joelanford already added the check for this file down in release.sh.

[prune]
go-tests = true
unused-packages = true
`
51 changes: 51 additions & 0 deletions pkg/scaffold/helm/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2019 The Operator-SDK Authors
//
// 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 helm

import (
"path/filepath"

"github.com/operator-framework/operator-sdk/pkg/scaffold/input"
)

// Main - main source file for helm operator
type Main struct {
input.Input
}

func (m *Main) GetInput() (input.Input, error) {
if m.Path == "" {
m.Path = filepath.Join("cmd", "manager", "main.go")
}
m.TemplateBody = mainTmpl
return m.Input, nil
}

const mainTmpl = `package main
import (
hoflags "github.com/operator-framework/operator-sdk/pkg/helm/flags"
"github.com/operator-framework/operator-sdk/pkg/helm"
"github.com/spf13/pflag"
)
func main() {
hflags := hoflags.AddTo(pflag.CommandLine)
pflag.Parse()
helm.Run(hflags)
}
`
50 changes: 50 additions & 0 deletions pkg/scaffold/helm/usersetup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2019 The Operator-SDK Authors
//
// 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 helm

import (
"path/filepath"

"github.com/operator-framework/operator-sdk/pkg/scaffold/input"
)

// UserSetup - userSetup script
type UserSetup struct {
input.Input
}

func (u *UserSetup) GetInput() (input.Input, error) {
if u.Path == "" {
u.Path = filepath.Join("bin", "user_setup")
}
u.TemplateBody = userSetupTmpl
u.IsExec = true
return u.Input, nil
}

const userSetupTmpl = `#!/bin/sh
set -x
# ensure $HOME exists and is accessible by group 0 (we don't know what the runtime UID will be)
mkdir -p ${HOME}
chown ${USER_UID}:0 ${HOME}
chmod ug+rwx ${HOME}
# runtime user will need to be able to self-insert in /etc/passwd
chmod g+rw /etc/passwd
# no need for this script to remain in the image after running
rm $0
`
6 changes: 6 additions & 0 deletions release.sh
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@ fi
VER_FILE="version/version.go"
TOML_TMPL_FILE="pkg/scaffold/gopkgtoml.go"
ANS_TOML_TMPL_FILE="pkg/scaffold/ansible/gopkgtoml.go"
HELM_TOML_TMPL_FILE="pkg/scaffold/helm/gopkgtoml.go"
CURR_VER_VER_FILE="$(sed -nr 's/Version = "(.+)"/\1/p' "$VER_FILE" | tr -d ' \t\n')"
CURR_VER_TMPL_FILE="$(sed -nr 's/.*".*v(.+)".*#osdk_version_annotation/v\1/p' "$TOML_TMPL_FILE" | tr -d ' \t\n')"
if [[ "$VER" != "$CURR_VER_VER_FILE" || "$VER" != "$CURR_VER_TMPL_FILE" ]]; then
@@ -44,6 +45,11 @@ if [[ "$VER" != "$CURR_VER_ANS_TMPL_FILE" ]]; then
echo "versions are not set correctly in $ANS_TOML_TMPL_FILE"
exit 1
fi
CURR_VER_HELM_TMPL_FILE="$(sed -nr 's/.*".*v(.+)".*#osdk_version_annotation/v\1/p' "$HELM_TOML_TMPL_FILE" | tr -d ' \t\n')"
if [[ "$VER" != "$CURR_VER_HELM_TMPL_FILE" ]]; then
echo "versions are not set correctly in $HELM_TOML_TMPL_FILE"
exit 1
fi

git tag --sign --message "Operator SDK $VER" "$VER"

16 changes: 0 additions & 16 deletions test/helm-operator/Dockerfile

This file was deleted.

2 changes: 0 additions & 2 deletions test/helm-operator/README.md

This file was deleted.

13 changes: 0 additions & 13 deletions test/helm-operator/bin/entrypoint

This file was deleted.

13 changes: 0 additions & 13 deletions test/helm-operator/bin/user_setup

This file was deleted.