about summary refs log tree commit diff
path: root/ops/kontemplate/main.go
diff options
context:
space:
mode:
authorVincent Ambo <tazjin@google.com>2019-12-20T22·13+0000
committerVincent Ambo <tazjin@google.com>2019-12-20T22·13+0000
commit795a97466527a5f02e79e47b7fb316c78ffde667 (patch)
tree541912f41f01aa8ae5952030df6d3aeb7bd3baa3 /ops/kontemplate/main.go
parent064f65dec295144dc8d440ebcf63f08eb154169d (diff)
chore(kontemplate): Prepare kontemplate for depot-merge
This merge will not yet include moving over to buildGo.nix, as support
for testing and such is not present in that library yet.
Diffstat (limited to 'ops/kontemplate/main.go')
-rw-r--r--ops/kontemplate/main.go242
1 files changed, 242 insertions, 0 deletions
diff --git a/ops/kontemplate/main.go b/ops/kontemplate/main.go
new file mode 100644
index 0000000000..e55d42465c
--- /dev/null
+++ b/ops/kontemplate/main.go
@@ -0,0 +1,242 @@
+// Copyright (C) 2016-2019  Vincent Ambo <mail@tazj.in>
+//
+// Kontemplate is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package main
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"strings"
+
+	"github.com/tazjin/kontemplate/context"
+	"github.com/tazjin/kontemplate/templater"
+	"gopkg.in/alecthomas/kingpin.v2"
+)
+
+const version string = "1.8.0"
+
+// This variable will be initialised by the Go linker during the builder
+var gitHash string
+
+var (
+	app = kingpin.New("kontemplate", "simple Kubernetes resource templating")
+
+	// Global flags
+	includes   = app.Flag("include", "Resource sets to include explicitly").Short('i').Strings()
+	excludes   = app.Flag("exclude", "Resource sets to exclude explicitly").Short('e').Strings()
+	variables  = app.Flag("var", "Provide variables to templates explicitly").Strings()
+	kubectlBin = app.Flag("kubectl", "Path to the kubectl binary (default 'kubectl')").Default("kubectl").String()
+
+	// Commands
+	template          = app.Command("template", "Template resource sets and print them")
+	templateFile      = template.Arg("file", "Cluster configuration file to use").Required().String()
+	templateOutputDir = template.Flag("output", "Output directory in which to save templated files instead of printing them").Short('o').String()
+
+	apply       = app.Command("apply", "Template resources and pass to 'kubectl apply'")
+	applyFile   = apply.Arg("file", "Cluster configuration file to use").Required().String()
+	applyDryRun = apply.Flag("dry-run", "Print remote operations without executing them").Default("false").Bool()
+
+	replace     = app.Command("replace", "Template resources and pass to 'kubectl replace'")
+	replaceFile = replace.Arg("file", "Cluster configuration file to use").Required().String()
+
+	delete     = app.Command("delete", "Template resources and pass to 'kubectl delete'")
+	deleteFile = delete.Arg("file", "Cluster configuration file to use").Required().String()
+
+	create     = app.Command("create", "Template resources and pass to 'kubectl create'")
+	createFile = create.Arg("file", "Cluster configuration file to use").Required().String()
+
+	versionCmd = app.Command("version", "Show kontemplate version")
+)
+
+func main() {
+	app.HelpFlag.Short('h')
+
+	switch kingpin.MustParse(app.Parse(os.Args[1:])) {
+	case template.FullCommand():
+		templateCommand()
+
+	case apply.FullCommand():
+		applyCommand()
+
+	case replace.FullCommand():
+		replaceCommand()
+
+	case delete.FullCommand():
+		deleteCommand()
+
+	case create.FullCommand():
+		createCommand()
+
+	case versionCmd.FullCommand():
+		versionCommand()
+	}
+}
+
+func versionCommand() {
+	if gitHash == "" {
+		fmt.Printf("Kontemplate version %s (git commit unknown)\n", version)
+	} else {
+		fmt.Printf("Kontemplate version %s (git commit: %s)\n", version, gitHash)
+	}
+}
+
+func templateCommand() {
+	_, resourceSets := loadContextAndResources(templateFile)
+
+	for _, rs := range *resourceSets {
+		if len(rs.Resources) == 0 {
+			fmt.Fprintf(os.Stderr, "Warning: Resource set '%s' does not exist or contains no valid templates\n", rs.Name)
+			continue
+		}
+
+		if *templateOutputDir != "" {
+			templateIntoDirectory(templateOutputDir, rs)
+		} else {
+			for _, r := range rs.Resources {
+				fmt.Fprintf(os.Stderr, "Rendered file %s/%s:\n", rs.Name, r.Filename)
+				fmt.Println(r.Rendered)
+			}
+		}
+	}
+}
+
+func templateIntoDirectory(outputDir *string, rs templater.RenderedResourceSet) {
+	// Attempt to create the output directory if it does not
+	// already exist:
+	if err := os.MkdirAll(*templateOutputDir, 0775); err != nil {
+		app.Fatalf("Could not create output directory: %v\n", err)
+	}
+
+	// Nested resource sets may contain slashes in their names.
+	// These are replaced with dashes for the purpose of writing a
+	// flat list of output files:
+	setName := strings.Replace(rs.Name, "/", "-", -1)
+
+	for _, r := range rs.Resources {
+		filename := fmt.Sprintf("%s/%s-%s", *templateOutputDir, setName, r.Filename)
+		fmt.Fprintf(os.Stderr, "Writing file %s\n", filename)
+
+		file, err := os.Create(filename)
+		if err != nil {
+			app.Fatalf("Could not create file %s: %v\n", filename, err)
+		}
+
+		_, err = fmt.Fprintf(file, r.Rendered)
+		if err != nil {
+			app.Fatalf("Error writing file %s: %v\n", filename, err)
+		}
+	}
+}
+
+func applyCommand() {
+	ctx, resources := loadContextAndResources(applyFile)
+
+	var kubectlArgs []string
+
+	if *applyDryRun {
+		kubectlArgs = []string{"apply", "-f", "-", "--dry-run"}
+	} else {
+		kubectlArgs = []string{"apply", "-f", "-"}
+	}
+
+	if err := runKubectlWithResources(ctx, &kubectlArgs, resources); err != nil {
+		failWithKubectlError(err)
+	}
+}
+
+func replaceCommand() {
+	ctx, resources := loadContextAndResources(replaceFile)
+	args := []string{"replace", "--save-config=true", "-f", "-"}
+
+	if err := runKubectlWithResources(ctx, &args, resources); err != nil {
+		failWithKubectlError(err)
+	}
+}
+
+func deleteCommand() {
+	ctx, resources := loadContextAndResources(deleteFile)
+	args := []string{"delete", "-f", "-"}
+
+	if err := runKubectlWithResources(ctx, &args, resources); err != nil {
+		failWithKubectlError(err)
+	}
+}
+
+func createCommand() {
+	ctx, resources := loadContextAndResources(createFile)
+	args := []string{"create", "--save-config=true", "-f", "-"}
+
+	if err := runKubectlWithResources(ctx, &args, resources); err != nil {
+		failWithKubectlError(err)
+	}
+}
+
+func loadContextAndResources(file *string) (*context.Context, *[]templater.RenderedResourceSet) {
+	ctx, err := context.LoadContext(*file, variables)
+	if err != nil {
+		app.Fatalf("Error loading context: %v\n", err)
+	}
+
+	resources, err := templater.LoadAndApplyTemplates(includes, excludes, ctx)
+	if err != nil {
+		app.Fatalf("Error templating resource sets: %v\n", err)
+	}
+
+	return ctx, &resources
+}
+
+func runKubectlWithResources(c *context.Context, kubectlArgs *[]string, resourceSets *[]templater.RenderedResourceSet) error {
+	argsWithContext := append(*kubectlArgs, fmt.Sprintf("--context=%s", c.Name))
+
+	for _, rs := range *resourceSets {
+		if len(rs.Resources) == 0 {
+			fmt.Fprintf(os.Stderr, "Warning: Resource set '%s' contains no valid templates\n", rs.Name)
+			continue
+		}
+
+		argsWithResourceSetArgs := append(argsWithContext, rs.Args...)
+
+		kubectl := exec.Command(*kubectlBin, argsWithResourceSetArgs...)
+
+		stdin, err := kubectl.StdinPipe()
+		if err != nil {
+			return fmt.Errorf("kubectl error: %v", err)
+		}
+
+		kubectl.Stdout = os.Stdout
+		kubectl.Stderr = os.Stderr
+
+		if err = kubectl.Start(); err != nil {
+			return fmt.Errorf("kubectl error: %v", err)
+		}
+
+		for _, r := range rs.Resources {
+			fmt.Printf("Passing file %s/%s to kubectl\n", rs.Name, r.Filename)
+			fmt.Fprintln(stdin, r.Rendered)
+		}
+		stdin.Close()
+
+		if err = kubectl.Wait(); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func failWithKubectlError(err error) {
+	app.Fatalf("Kubectl error: %v\n", err)
+}