Skip to content
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

Add CLI command to generate k8s manifests #1046

Merged
merged 15 commits into from
May 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .ci/run-e2e-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ elif [ "${TEST_GROUP}" = "es-token-propagation" ]
then
echo "Running token propagation tests"
make e2e-tests-token-propagation-es
elif [ "${TEST_GROUP}" = "generate" ]
then
echo "Running CLI manifest generatation tests"
make e2e-tests-generate
else
echo "Unknown TEST_GROUP [${TEST_GROUP}]"; exit 1
fi
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/e2e-kubernetes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
runs-on: ubuntu-16.04
strategy:
matrix:
TEST_GROUP: [smoke, es, cassandra, streaming, examples1, examples2]
TEST_GROUP: [smoke, es, cassandra, streaming, examples1, examples2, generate]
steps:
- uses: actions/setup-go@v1
with:
Expand Down
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ unit-tests:
@go test $(VERBOSE) $(UNIT_TEST_PACKAGES) -cover -coverprofile=cover.out -ldflags $(LD_FLAGS)

.PHONY: e2e-tests
e2e-tests: prepare-e2e-tests e2e-tests-smoke e2e-tests-cassandra e2e-tests-es e2e-tests-self-provisioned-es e2e-tests-streaming e2e-tests-examples1 e2e-tests-examples2 e2e-tests-examples-openshift
e2e-tests: prepare-e2e-tests e2e-tests-smoke e2e-tests-cassandra e2e-tests-es e2e-tests-self-provisioned-es e2e-tests-streaming e2e-tests-examples1 e2e-tests-examples2 e2e-tests-examples-openshift e2e-tests-generate

.PHONY: prepare-e2e-tests
prepare-e2e-tests: build docker push
Expand All @@ -109,6 +109,11 @@ e2e-tests-smoke: prepare-e2e-tests
@echo Running Smoke end-to-end tests...
@BUILD_IMAGE=$(BUILD_IMAGE) go test -tags=smoke ./test/e2e/... $(TEST_OPTIONS)

.PHONY: e2e-tests-generate
e2e-tests-generate: prepare-e2e-tests
@echo Running generate end-to-end tests...
@BUILD_IMAGE=$(BUILD_IMAGE) go test -tags=generate ./test/e2e/... $(TEST_OPTIONS)

.PHONY: e2e-tests-cassandra
e2e-tests-cassandra: prepare-e2e-tests cassandra
@echo Running Cassandra end-to-end tests...
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ In this example, the Jaeger UI is available at http://192.168.122.34.

The official documentation for the Jaeger Operator, including all its customization options, are available under the main [Jaeger Documentation](https://www.jaegertracing.io/docs/latest/operator/).


## (experimental) Generate Kubernetes manifest file

Sometimes it is preferable to generate plain manifests files instead of running an operator in a cluster. `jaeger-operator generate` generates kubernetes manifests from a given CR. In this example we apply the manifest generated by [examples/simplest.yaml](https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/examples/simplest.yaml) to the namespace `jaeger-test`:

```bash
curl https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/examples/simplest.yaml | docker run --rm jaegertracing/jaeger-operator:master generate | kubectl apply -n jaeger-test -f -
```

It is recommended to deploy the operator instead of generating a static manifest.

## Contributing and Developing

Please see [CONTRIBUTING.md](CONTRIBUTING.md).
Expand Down
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/jaegertracing/jaeger-operator/pkg/cmd/generate"
"github.com/jaegertracing/jaeger-operator/pkg/cmd/start"
"github.com/jaegertracing/jaeger-operator/pkg/cmd/version"
)
Expand Down Expand Up @@ -37,6 +38,7 @@ func init() {

RootCmd.AddCommand(start.NewStartCommand())
RootCmd.AddCommand(version.NewVersionCommand())
RootCmd.AddCommand(generate.NewGenerateCommand())
}

// initConfig reads in config file and ENV variables if set.
Expand Down
4 changes: 4 additions & 0 deletions pkg/account/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ func Get(jaeger *v1.Jaeger) []*corev1.ServiceAccount {
func getMain(jaeger *v1.Jaeger) *corev1.ServiceAccount {
trueVar := true
return &corev1.ServiceAccount{
TypeMeta: metav1.TypeMeta{
chlunde marked this conversation as resolved.
Show resolved Hide resolved
APIVersion: "v1",
Kind: "ServiceAccount",
},
ObjectMeta: metav1.ObjectMeta{
Name: JaegerServiceAccountFor(jaeger, ""),
Namespace: jaeger.Namespace,
Expand Down
111 changes: 111 additions & 0 deletions pkg/cmd/generate/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package generate

import (
"context"
"fmt"
"io"
"os"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8s_json "k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/apimachinery/pkg/util/yaml"

v1 "github.com/jaegertracing/jaeger-operator/pkg/apis/jaegertracing/v1"
"github.com/jaegertracing/jaeger-operator/pkg/cmd/start"
"github.com/jaegertracing/jaeger-operator/pkg/strategy"
)

// NewGenerateCommand starts the Jaeger Operator
func NewGenerateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "generate",
Short: "(experimental) Generate YAML manifests from Jaeger CRD",
Long: `Generate YAML manifests from Jaeger CRD.

Defaults to reading Jaeger CRD from standard input and writing the manifest file to standard output, override with --cr <filename> and --output <filename>.`,
RunE: generate,
}

cmd.Flags().String("cr", "/dev/stdin", "Input Jaeger CRD")
viper.BindPFlag("cr", cmd.Flags().Lookup("cr"))

cmd.Flags().String("output", "/dev/stdout", "Where to print the generated YAML documents")
chlunde marked this conversation as resolved.
Show resolved Hide resolved
viper.BindPFlag("output", cmd.Flags().Lookup("output"))

start.AddFlags(cmd)

return cmd
}

func createSpecFromYAML(filename string) (*v1.Jaeger, error) {
// #nosec G304: Potential file inclusion via variable
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()

var spec v1.Jaeger
decoder := yaml.NewYAMLOrJSONDecoder(f, 8192)
if err := decoder.Decode(&spec); err != nil && err != io.EOF {
return nil, err
}

return &spec, nil
}

func generate(_ *cobra.Command, _ []string) error {
level, err := log.ParseLevel(viper.GetString("log-level"))
if err != nil {
log.SetLevel(log.InfoLevel)
} else {
log.SetLevel(level)
pavolloffay marked this conversation as resolved.
Show resolved Hide resolved
}

input := viper.GetString("cr")
if input == "/dev/stdin" {
// Reading from stdin by default is neat when running as a
// container instead of a binary, but is confusing when no
// input is sent and the program just hangs
log.Info("Reading Jaeger CRD from standard input (use --cr <filename> to override)")
}

spec, err := createSpecFromYAML(input)
if err != nil {
return err
}

s := strategy.For(context.Background(), spec)

outputName := viper.GetString("output")
out, err := os.OpenFile(outputName, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
if err != nil {
return err
}

defer out.Close()

encoder := k8s_json.NewYAMLSerializer(k8s_json.DefaultMetaFactory, nil, nil)
for _, obj := range s.All() {
// OwnerReferences normally references the CR, but it is not a
// resource in the cluster so we must remove it

type f interface {
SetOwnerReferences(references []metav1.OwnerReference)
}

meta := obj.(f)
meta.SetOwnerReferences(nil)

fmt.Fprintln(out, "---")
if err := encoder.Encode(obj, out); err != nil {
log.Fatal(err)
}
}

return nil
}
46 changes: 27 additions & 19 deletions pkg/cmd/start/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,12 @@ import (
"github.com/jaegertracing/jaeger-operator/pkg/version"
)

// NewStartCommand starts the Jaeger Operator
func NewStartCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "start",
Short: "Starts a new Jaeger Operator",
Long: "Starts a new Jaeger Operator",
Run: func(cmd *cobra.Command, args []string) {
start(cmd, args)
},
}
// AddFlags adds all command line flags related to manifest
// generation. They are shared between the operator and CLI `generate`
// command.
func AddFlags(cmd *cobra.Command) {
cmd.Flags().String("log-level", "info", "The log-level for the operator. Possible values: trace, debug, info, warning, error, fatal, panic")
viper.BindPFlag("log-level", cmd.Flags().Lookup("log-level"))

cmd.Flags().String("jaeger-version", version.DefaultJaeger(), "Deprecated: the Jaeger version is now managed entirely by the operator. This option is currently no-op.")

Expand Down Expand Up @@ -70,9 +66,28 @@ func NewStartCommand() *cobra.Command {
cmd.Flags().String("kafka-provision", "auto", "Whether to auto-provision a Kafka cluster for suitable Jaeger instances. Possible values: 'yes', 'no', 'auto'. When set to 'auto' and the API name 'kafka.strimzi.io' is available, auto-provisioning is enabled.")
viper.BindPFlag("kafka-provision", cmd.Flags().Lookup("kafka-provision"))

cmd.Flags().String("log-level", "info", "The log-level for the operator. Possible values: trace, debug, info, warning, error, fatal, panic")
viper.BindPFlag("log-level", cmd.Flags().Lookup("log-level"))
docURL := fmt.Sprintf("https://www.jaegertracing.io/docs/%s", version.DefaultJaegerMajorMinor())
cmd.Flags().String("documentation-url", docURL, "The URL for the 'Documentation' menu item")
viper.BindPFlag("documentation-url", cmd.Flags().Lookup("documentation-url"))

cmd.Flags().Bool("kafka-provisioning-minimal", false, "(unsupported) Whether to provision Kafka clusters with minimal requirements, suitable for demos and tests.")
viper.BindPFlag("kafka-provisioning-minimal", cmd.Flags().Lookup("kafka-provisioning-minimal"))
}

// NewStartCommand starts the Jaeger Operator
func NewStartCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "start",
Short: "Starts a new Jaeger Operator",
Long: "Starts a new Jaeger Operator",
Run: func(cmd *cobra.Command, args []string) {
start(cmd, args)
},
}

AddFlags(cmd)

// Operator specific flags here. Any flags affecting manifest generation should be added to AddFlags instead
cmd.Flags().String("metrics-host", "0.0.0.0", "The host to bind the metrics port")
viper.BindPFlag("metrics-host", cmd.Flags().Lookup("metrics-host"))

Expand All @@ -82,19 +97,12 @@ func NewStartCommand() *cobra.Command {
cmd.Flags().Int32("cr-metrics-port", 8686, "The metrics port for Operator and/or Custom Resource based metrics")
viper.BindPFlag("cr-metrics-port", cmd.Flags().Lookup("cr-metrics-port"))

docURL := fmt.Sprintf("https://www.jaegertracing.io/docs/%s", version.DefaultJaegerMajorMinor())
cmd.Flags().String("documentation-url", docURL, "The URL for the 'Documentation' menu item")
viper.BindPFlag("documentation-url", cmd.Flags().Lookup("documentation-url"))

cmd.Flags().String("jaeger-agent-hostport", "localhost:6831", "The location for the Jaeger Agent")
viper.BindPFlag("jaeger-agent-hostport", cmd.Flags().Lookup("jaeger-agent-hostport"))

cmd.Flags().Bool("tracing-enabled", false, "Whether the Operator should report its own spans to a Jaeger instance")
viper.BindPFlag("tracing-enabled", cmd.Flags().Lookup("tracing-enabled"))

cmd.Flags().Bool("kafka-provisioning-minimal", false, "(unsupported) Whether to provision Kafka clusters with minimal requirements, suitable for demos and tests.")
viper.BindPFlag("kafka-provisioning-minimal", cmd.Flags().Lookup("kafka-provisioning-minimal"))

return cmd
}

Expand Down
74 changes: 73 additions & 1 deletion pkg/strategy/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
rbac "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/runtime"

v1 "github.com/jaegertracing/jaeger-operator/pkg/apis/jaegertracing/v1"
kafkav1beta1 "github.com/jaegertracing/jaeger-operator/pkg/apis/kafka/v1beta1"
Expand All @@ -17,7 +18,8 @@ import (

// S knows what type of deployments to build based on a given spec
type S struct {
typ v1.DeploymentStrategy
typ v1.DeploymentStrategy
// When adding a new type here, remember to update All() too
accounts []corev1.ServiceAccount
clusterRoleBindings []rbac.ClusterRoleBinding
configMaps []corev1.ConfigMap
Expand Down Expand Up @@ -209,3 +211,73 @@ func (s S) Secrets() []corev1.Secret {
func (s S) Dependencies() []batchv1.Job {
return s.dependencies
}

// All returns the list of all objects for this strategy
func (s S) All() []runtime.Object {
chlunde marked this conversation as resolved.
Show resolved Hide resolved
var ret []runtime.Object

// Keep ordering close to
// https://github.com/kubernetes-sigs/kustomize/blob/master/api/resid/gvk.go#L77-L103

for _, o := range s.accounts {
ret = append(ret, o.DeepCopy())
}

for _, o := range s.clusterRoleBindings {
ret = append(ret, o.DeepCopy())
}

for _, o := range s.configMaps {
ret = append(ret, o.DeepCopy())
}

for _, o := range s.cronJobs {
ret = append(ret, o.DeepCopy())
}

for _, o := range s.elasticsearches {
ret = append(ret, o.DeepCopy())
}

for _, o := range s.ingresses {
ret = append(ret, o.DeepCopy())
}

for _, o := range s.horizontalPodAutoscalers {
ret = append(ret, o.DeepCopy())
}

for _, o := range s.kafkas {
ret = append(ret, o.DeepCopy())
}

for _, o := range s.kafkaUsers {
ret = append(ret, o.DeepCopy())
}

for _, o := range s.routes {
ret = append(ret, o.DeepCopy())
}

for _, o := range s.services {
ret = append(ret, o.DeepCopy())
}

for _, o := range s.secrets {
ret = append(ret, o.DeepCopy())
}

for _, o := range s.dependencies {
ret = append(ret, o.DeepCopy())
}

for _, o := range s.daemonSets {
ret = append(ret, o.DeepCopy())
}

for _, o := range s.deployments {
ret = append(ret, o.DeepCopy())
}

return ret
}
Loading