This does several changes to the new "template to directory" feature introduced in the previous commit: 1. Errors are now "handled". In classic Go-style, it is of course all too easy to do absolutely nothing with errors, but we can't have that. I'm onto you, Renee French's husband! 2. Resource sets containing similarly named files are now templated correctly by prepending the resource set name to the filename. 3. In the same vein as the previous point, nested resource sets are now also output correctly by replacing slashes (`/`) with dashes (`-`) to guarantee that the output files are a flat list. Some minor cosmetic fixes have also been applied.
239 lines
6.9 KiB
Go
239 lines
6.9 KiB
Go
// 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/>.
|
|
|
|
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.4.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()
|
|
|
|
// 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.LoadContextFromFile(*file)
|
|
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 {
|
|
args := 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
|
|
}
|
|
|
|
kubectl := exec.Command("kubectl", args...)
|
|
|
|
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) {
|
|
fmt.Fprintf(os.Stderr, "Kubectl error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|