2017-11-21 11:24:04 +01:00
|
|
|
// Copyright (C) 2016-2017 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/>.
|
|
|
|
|
2017-02-08 11:51:08 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2017-02-08 11:55:59 +01:00
|
|
|
"os"
|
2017-02-08 15:32:44 +01:00
|
|
|
"os/exec"
|
2018-05-08 11:15:57 +02:00
|
|
|
"strings"
|
2017-02-08 11:51:08 +01:00
|
|
|
|
|
|
|
"github.com/tazjin/kontemplate/context"
|
|
|
|
"github.com/tazjin/kontemplate/templater"
|
2017-05-04 18:32:26 +02:00
|
|
|
"gopkg.in/alecthomas/kingpin.v2"
|
2017-02-08 11:51:08 +01:00
|
|
|
)
|
|
|
|
|
2018-05-08 11:27:35 +02:00
|
|
|
const version string = "1.5.0"
|
2017-05-08 13:47:43 +02:00
|
|
|
|
2017-05-08 11:08:21 +02:00
|
|
|
// This variable will be initialised by the Go linker during the builder
|
|
|
|
var gitHash string
|
|
|
|
|
2017-05-04 18:32:26 +02:00
|
|
|
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()
|
2018-05-30 21:43:31 +02:00
|
|
|
variables = app.Flag("var", "Provide variables to templates explicitly").Strings()
|
2017-05-04 18:32:26 +02:00
|
|
|
|
|
|
|
// Commands
|
2018-04-23 07:12:38 +02:00
|
|
|
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()
|
2017-05-04 18:32:26 +02:00
|
|
|
|
|
|
|
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()
|
2017-05-08 11:08:21 +02:00
|
|
|
|
|
|
|
versionCmd = app.Command("version", "Show kontemplate version")
|
2017-05-04 18:32:26 +02:00
|
|
|
)
|
|
|
|
|
2017-02-08 11:51:08 +01:00
|
|
|
func main() {
|
2017-05-04 18:32:26 +02:00
|
|
|
app.HelpFlag.Short('h')
|
2017-02-08 11:51:08 +01:00
|
|
|
|
2017-05-04 18:32:26 +02:00
|
|
|
switch kingpin.MustParse(app.Parse(os.Args[1:])) {
|
|
|
|
case template.FullCommand():
|
|
|
|
templateCommand()
|
2017-02-08 11:51:08 +01:00
|
|
|
|
2017-05-04 18:32:26 +02:00
|
|
|
case apply.FullCommand():
|
|
|
|
applyCommand()
|
2017-02-08 11:51:08 +01:00
|
|
|
|
2017-05-04 18:32:26 +02:00
|
|
|
case replace.FullCommand():
|
|
|
|
replaceCommand()
|
2017-02-08 12:58:53 +01:00
|
|
|
|
2017-05-04 18:32:26 +02:00
|
|
|
case delete.FullCommand():
|
|
|
|
deleteCommand()
|
|
|
|
|
|
|
|
case create.FullCommand():
|
|
|
|
createCommand()
|
2017-05-08 11:08:21 +02:00
|
|
|
|
|
|
|
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)
|
2017-02-08 15:32:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-04 18:32:26 +02:00
|
|
|
func templateCommand() {
|
2017-06-11 21:43:02 +02:00
|
|
|
_, resourceSets := loadContextAndResources(templateFile)
|
2017-05-04 18:32:26 +02:00
|
|
|
|
2017-06-11 21:43:02 +02:00
|
|
|
for _, rs := range *resourceSets {
|
2017-08-22 19:04:32 +02:00
|
|
|
if len(rs.Resources) == 0 {
|
2017-10-27 02:39:16 +02:00
|
|
|
fmt.Fprintf(os.Stderr, "Warning: Resource set '%s' does not exist or contains no valid templates\n", rs.Name)
|
2017-10-27 02:36:27 +02:00
|
|
|
continue
|
2017-08-22 19:04:32 +02:00
|
|
|
}
|
|
|
|
|
2018-05-08 11:15:57 +02:00
|
|
|
if *templateOutputDir != "" {
|
|
|
|
templateIntoDirectory(templateOutputDir, rs)
|
|
|
|
} else {
|
|
|
|
for _, r := range rs.Resources {
|
|
|
|
fmt.Fprintf(os.Stderr, "Rendered file %s/%s:\n", rs.Name, r.Filename)
|
2018-04-23 07:12:38 +02:00
|
|
|
fmt.Println(r.Rendered)
|
|
|
|
}
|
2017-06-11 21:43:02 +02:00
|
|
|
}
|
2017-02-08 16:19:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-08 11:15:57 +02:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-04 18:32:26 +02:00
|
|
|
func applyCommand() {
|
|
|
|
ctx, resources := loadContextAndResources(applyFile)
|
|
|
|
|
|
|
|
var kubectlArgs []string
|
|
|
|
|
|
|
|
if *applyDryRun {
|
|
|
|
kubectlArgs = []string{"apply", "-f", "-", "--dry-run"}
|
|
|
|
} else {
|
|
|
|
kubectlArgs = []string{"apply", "-f", "-"}
|
2017-02-08 16:19:10 +01:00
|
|
|
}
|
2017-05-04 18:32:26 +02:00
|
|
|
|
2017-05-08 14:29:47 +02:00
|
|
|
if err := runKubectlWithResources(ctx, &kubectlArgs, resources); err != nil {
|
|
|
|
failWithKubectlError(err)
|
|
|
|
}
|
2017-05-04 18:32:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func replaceCommand() {
|
|
|
|
ctx, resources := loadContextAndResources(replaceFile)
|
|
|
|
args := []string{"replace", "--save-config=true", "-f", "-"}
|
2017-05-08 14:29:47 +02:00
|
|
|
|
|
|
|
if err := runKubectlWithResources(ctx, &args, resources); err != nil {
|
|
|
|
failWithKubectlError(err)
|
|
|
|
}
|
2017-05-04 18:32:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func deleteCommand() {
|
|
|
|
ctx, resources := loadContextAndResources(deleteFile)
|
|
|
|
args := []string{"delete", "-f", "-"}
|
2017-05-08 14:29:47 +02:00
|
|
|
|
|
|
|
if err := runKubectlWithResources(ctx, &args, resources); err != nil {
|
|
|
|
failWithKubectlError(err)
|
|
|
|
}
|
2017-02-08 16:19:10 +01:00
|
|
|
}
|
2017-02-08 15:32:44 +01:00
|
|
|
|
2017-05-04 18:32:26 +02:00
|
|
|
func createCommand() {
|
|
|
|
ctx, resources := loadContextAndResources(createFile)
|
|
|
|
args := []string{"create", "--save-config=true", "-f", "-"}
|
2017-05-08 14:29:47 +02:00
|
|
|
|
|
|
|
if err := runKubectlWithResources(ctx, &args, resources); err != nil {
|
|
|
|
failWithKubectlError(err)
|
|
|
|
}
|
2017-05-04 18:32:26 +02:00
|
|
|
}
|
|
|
|
|
2017-06-11 21:34:22 +02:00
|
|
|
func loadContextAndResources(file *string) (*context.Context, *[]templater.RenderedResourceSet) {
|
2017-05-04 18:32:26 +02:00
|
|
|
ctx, err := context.LoadContextFromFile(*file)
|
|
|
|
if err != nil {
|
|
|
|
app.Fatalf("Error loading context: %v\n", err)
|
|
|
|
}
|
|
|
|
|
2018-05-30 21:43:31 +02:00
|
|
|
err = ctx.SetVariablesFromArguments(variables)
|
|
|
|
if err != nil {
|
|
|
|
app.Fatalf("Error setting explicit variables in context: %v\n", err)
|
|
|
|
}
|
|
|
|
|
2017-06-11 21:34:22 +02:00
|
|
|
resources, err := templater.LoadAndApplyTemplates(includes, excludes, ctx)
|
2017-05-04 18:32:26 +02:00
|
|
|
if err != nil {
|
|
|
|
app.Fatalf("Error templating resource sets: %v\n", err)
|
2017-02-08 18:06:47 +01:00
|
|
|
}
|
2017-05-04 18:32:26 +02:00
|
|
|
|
|
|
|
return ctx, &resources
|
2017-02-08 18:06:47 +01:00
|
|
|
}
|
|
|
|
|
2017-06-11 21:34:22 +02:00
|
|
|
func runKubectlWithResources(c *context.Context, kubectlArgs *[]string, resourceSets *[]templater.RenderedResourceSet) error {
|
2017-02-08 16:19:10 +01:00
|
|
|
args := append(*kubectlArgs, fmt.Sprintf("--context=%s", c.Name))
|
2017-02-08 11:51:08 +01:00
|
|
|
|
2017-08-22 19:04:32 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-06-11 21:34:22 +02:00
|
|
|
kubectl := exec.Command("kubectl", args...)
|
2017-02-08 11:51:08 +01:00
|
|
|
|
2017-06-11 21:34:22 +02:00
|
|
|
stdin, err := kubectl.StdinPipe()
|
|
|
|
if err != nil {
|
2018-03-09 15:17:54 +01:00
|
|
|
return fmt.Errorf("kubectl error: %v", err)
|
2017-06-11 21:34:22 +02:00
|
|
|
}
|
2017-02-08 15:32:44 +01:00
|
|
|
|
2017-06-11 21:34:22 +02:00
|
|
|
kubectl.Stdout = os.Stdout
|
|
|
|
kubectl.Stderr = os.Stderr
|
2017-02-08 15:32:44 +01:00
|
|
|
|
2017-06-11 21:34:22 +02:00
|
|
|
if err = kubectl.Start(); err != nil {
|
2018-03-09 15:17:54 +01:00
|
|
|
return fmt.Errorf("kubectl error: %v", err)
|
2017-06-11 21:34:22 +02:00
|
|
|
}
|
2017-02-08 16:19:10 +01:00
|
|
|
|
2017-08-22 19:04:32 +02:00
|
|
|
for _, r := range rs.Resources {
|
|
|
|
fmt.Printf("Passing file %s/%s to kubectl\n", rs.Name, r.Filename)
|
2017-06-11 21:34:22 +02:00
|
|
|
fmt.Fprintln(stdin, r.Rendered)
|
|
|
|
}
|
|
|
|
stdin.Close()
|
|
|
|
|
|
|
|
if err = kubectl.Wait(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-02-08 11:51:08 +01:00
|
|
|
}
|
2017-02-08 16:19:10 +01:00
|
|
|
|
2017-06-11 21:34:22 +02:00
|
|
|
return nil
|
2017-02-08 11:51:08 +01:00
|
|
|
}
|
2017-05-08 14:29:47 +02:00
|
|
|
|
|
|
|
func failWithKubectlError(err error) {
|
2018-05-08 11:26:01 +02:00
|
|
|
app.Fatalf("Kubectl error: %v\n", err)
|
2017-05-08 14:29:47 +02:00
|
|
|
}
|