-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Doug Hatcher
committed
May 22, 2021
1 parent
52bd332
commit 1e3aab1
Showing
1 changed file
with
86 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,17 @@ | ||
/* | ||
Copyright © 2021 Doug Hatcher <[email protected]> | ||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. | ||
build command generates docker images, using templates, and pushes them to registries. This provides a useful | ||
method to rapidly build images with variants, and manage them in a git repository. Dockerfiles can be made | ||
supporting includes, conditionals, loops, etc. | ||
Each image should get it's own directory in the images directory, by default the current working dir. If mach | ||
build is ran with no arguments, it will try to build every image discovered. | ||
* images/<image_name>/Dockerfile is the default image to be built, this is a proper Dockerfile and should not | ||
use the templating system. | ||
* images/<image_name>/Dockerfile-<variant> The variant can be used to build alternate images, when pushed to | ||
the registry the variant is appended to the image name. | ||
* images/<image_name>/Dockerfile[-<variant>].tpl using the .tpl will process through the templating engine. This | ||
allows for including partial templates. | ||
*/ | ||
package cmd | ||
|
||
|
@@ -55,6 +49,8 @@ var firstOnly = false | |
|
||
var nopush bool = false | ||
|
||
var lastOutput = "begin" | ||
|
||
type ErrorLine struct { | ||
Error string `json:"error"` | ||
ErrorDetail ErrorDetail `json:"errorDetail"` | ||
|
@@ -83,19 +79,38 @@ func init() { | |
|
||
testMode = strings.HasSuffix(os.Args[0], ".test") | ||
|
||
// no-push flag prevents pushing to the docker registry, good for testing locally | ||
buildCmd.Flags().BoolP("no-push", "n", false, "Do not push to registry") | ||
|
||
// output-only does not build the image, just outputs the template to stdout | ||
buildCmd.Flags().BoolP("output-only", "o", false, "send output to stdout, do not build") | ||
|
||
// first-only will stop after processing the first image if more than one image is matched. Good to pair with output-only | ||
// if you intend to pipe output to a file | ||
buildCmd.Flags().BoolP("first-only", "f", false, "breaks after processing first image if more than one") | ||
|
||
// buildImageDirname tells the tool which directory to itereate through to find Dockerfiles. defaults the present working | ||
// directory, but a good practice is to mint a .mach.yaml and set this to `images` or the like when building an IaC repo. | ||
viper.SetDefault("buildImageDirname", ".") | ||
|
||
// defaultGitBranch allows for setting which branch does not add a branch variant to the tag. Default to main, consider | ||
// changing your branch name before chaning this default. | ||
viper.SetDefault("defaultGitBranch", "main") | ||
|
||
//docker_host is the URL for the docker registry, we default to the offical registry, but this can be changed in config | ||
// to any registry you like. | ||
viper.SetDefault("docker_host", "https://index.docker.io/v1/") | ||
|
||
if testMode { | ||
// docker_registry is the package name inside the docker registry. @todo think through a better name | ||
viper.SetDefault("docker_registry", "superterran/mach") | ||
} | ||
} | ||
|
||
// runBuild is the main flow for the build command. If no arguments are present, it will each through | ||
// the images directory and attempt to build every file matching the pattern `Dockerfile*`. If arguements are passed | ||
// it will attempt to match the strings with dockerfiles and build those only. This method sets several flags, | ||
// outputOnly is suitable for leveraging the stdout which provides the contents of a templated dockerfile. | ||
func runBuild(cmd *cobra.Command, args []string) error { | ||
|
||
outputonly, _ := cmd.Flags().GetBool("output-only") | ||
|
@@ -150,6 +165,12 @@ func runBuild(cmd *cobra.Command, args []string) error { | |
return nil | ||
} | ||
|
||
// generateTemplate grabs the docker tpl file, and any tpl files in the `includes` sub directory with the | ||
// docerfile, and runs them through a templater to produce the output for a dockerfile to be built. | ||
// this method uses the `html/template` package https://golang.org/pkg/html/template/ so this should be | ||
// fairly flexible. I intentionally haven't introduced outside variables to the templating engine i.e. | ||
// host system environment variables. This may come in time, but Dockerfiles should not contain secrets so | ||
// I'm not sure if this is a good feature to introduce. | ||
func generateTemplate(wr io.Writer, filename string) { | ||
|
||
tpl, err := template.ParseGlob(filename) | ||
|
@@ -162,6 +183,40 @@ func generateTemplate(wr io.Writer, filename string) { | |
|
||
} | ||
|
||
// getTag determines the string used for the docker image tag that gets referenced in the registry. | ||
// This is typically just the directory name the dockerfile resides in, but can be modified by either | ||
// using a variant in the filename (detailed below) or by using a git branch other than the default | ||
func getTag(filename string) string { | ||
|
||
var tag string = filepath.Base(filepath.Dir(filename)) + getVariant(filename) | ||
|
||
if viper.GetString("docker_registry") != "" { | ||
return viper.GetString("docker_registry") + ":" + tag | ||
} else { | ||
nopush = true | ||
return tag | ||
} | ||
|
||
} | ||
|
||
// getVariant determines the additional string to append to the docker image tag. This is determined | ||
// by the filename of the docker file being read, `Dockerfile` will not get any variant, but files like | ||
// `Dockerfile-variant` will be tagged as `<dirname>-<variant>`. | ||
func getVariant(filename string) string { | ||
|
||
var variant string = "" | ||
|
||
if strings.Contains(filepath.Base(filename), "-") { | ||
variant = "-" + strings.Split(filepath.Base(filename), "-")[1] | ||
} | ||
|
||
return strings.Replace(variant, ".tpl", "", 1) + getBranchVariant() | ||
|
||
} | ||
|
||
// getBranchVariant will return a string that can appended to the variant of a tag, this function is called | ||
// by getVariant. This will not produce output if on the default branch-name, otherwise it will return | ||
// a string with the branch. | ||
func getBranchVariant() string { | ||
|
||
var branch = "" | ||
|
@@ -190,31 +245,8 @@ func getBranchVariant() string { | |
return variant | ||
} | ||
|
||
func getVariant(filename string) string { | ||
|
||
var variant string = "" | ||
|
||
if strings.Contains(filepath.Base(filename), "-") { | ||
variant = "-" + strings.Split(filepath.Base(filename), "-")[1] | ||
} | ||
|
||
return strings.Replace(variant, ".tpl", "", 1) + getBranchVariant() | ||
|
||
} | ||
|
||
func getTag(filename string) string { | ||
|
||
var tag string = filepath.Base(filepath.Dir(filename)) + getVariant(filename) | ||
|
||
if viper.GetString("docker_registry") != "" { | ||
return viper.GetString("docker_registry") + ":" + tag | ||
} else { | ||
nopush = true | ||
return tag | ||
} | ||
|
||
} | ||
|
||
// buildImage probably does too much, but it creates a tarball with a templatized dockerfile, and | ||
// everything in it's directory for context, and it builds the image. | ||
func buildImage(filename string) string { | ||
|
||
var mach_tag = getTag(filename) | ||
|
@@ -280,10 +312,17 @@ func buildImage(filename string) string { | |
} | ||
} | ||
|
||
e := os.Remove(DockerFilename) | ||
if e != nil { | ||
log.Fatal(e) | ||
} | ||
|
||
return mach_tag | ||
|
||
} | ||
|
||
// pushImage takes the current tag and pushes it to the configured registry. This fuction short-circuits | ||
// if testMode or nopush are true. | ||
func pushImage(mach_tag string) string { | ||
|
||
if testMode { | ||
|
@@ -332,6 +371,8 @@ func pushImage(mach_tag string) string { | |
return "push complete" | ||
} | ||
|
||
// dockerLog is a logging method that tries to handle the output produced by the docker daemon. | ||
// it needs a bit of work. | ||
func dockerLog(msg string) string { | ||
|
||
var result map[string]interface{} | ||
|
@@ -350,7 +391,7 @@ func dockerLog(msg string) string { | |
case "aux": | ||
case "errorDetail": | ||
default: | ||
return value.(string) | ||
|
||
} | ||
} | ||
|
||
|