github.com/jonsyu1/godel@v0.0.0-20171017211503-64567a0cf169/apps/distgo/cmd/docker/build.go (about)

     1  // Copyright 2016 Palantir Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package docker
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"io/ioutil"
    21  	"os"
    22  	"path"
    23  
    24  	"github.com/pkg/errors"
    25  
    26  	"github.com/palantir/godel/apps/distgo/cmd/build"
    27  	"github.com/palantir/godel/apps/distgo/cmd/dist"
    28  	"github.com/palantir/godel/apps/distgo/params"
    29  )
    30  
    31  func Build(products []string, cfg params.Project, wd, baseRepo string, verbose bool, stdout io.Writer) error {
    32  	// the docker build tasks first runs dist task on the products
    33  	// on which the docker images have a dependency. after building the dists,
    34  	// the images are built in ordered way since the images can have dependencies among themselves.
    35  	productsToDist, productsToBuildImage, err := productsToDistAndBuildImage(products, cfg)
    36  	if err != nil {
    37  		return err
    38  	}
    39  
    40  	productsToDistRequired, err := dist.RequiresDist(productsToDist, cfg, wd)
    41  	if err != nil {
    42  		return err
    43  	}
    44  	if len(productsToDistRequired) != 0 {
    45  		// run the dist task
    46  		if err := dist.Products(productsToDistRequired, cfg, false, wd, stdout); err != nil {
    47  			return err
    48  		}
    49  	}
    50  
    51  	// build docker images
    52  	buildSpecsWithDeps, err := build.SpecsWithDepsForArgs(cfg, productsToBuildImage, wd)
    53  	if err != nil {
    54  		return err
    55  	}
    56  	orderedSpecs, err := OrderBuildSpecs(buildSpecsWithDeps)
    57  	if err != nil {
    58  		return err
    59  	}
    60  	if baseRepo != "" {
    61  		// if base repo is specified, join it to each image's repo
    62  		for i := range orderedSpecs {
    63  			for j := range orderedSpecs[i].Spec.DockerImages {
    64  				orderedSpecs[i].Spec.DockerImages[j].Repository = path.Join(baseRepo,
    65  					orderedSpecs[i].Spec.DockerImages[j].Repository)
    66  			}
    67  		}
    68  	}
    69  	return RunBuild(orderedSpecs, verbose, stdout)
    70  }
    71  
    72  func RunBuild(buildSpecsWithDeps []params.ProductBuildSpecWithDeps, verbose bool, stdout io.Writer) error {
    73  	specsMap := buildSpecsMap(buildSpecsWithDeps)
    74  	for i := range buildSpecsWithDeps {
    75  		for _, image := range buildSpecsWithDeps[i].Spec.DockerImages {
    76  			if err := buildImage(image, buildSpecsWithDeps[i], specsMap, verbose, stdout); err != nil {
    77  				return err
    78  			}
    79  		}
    80  	}
    81  	return nil
    82  }
    83  
    84  func buildImage(image params.DockerImage, buildSpecsWithDeps params.ProductBuildSpecWithDeps, specsMap map[string]params.ProductBuildSpecWithDeps, verbose bool, stdout io.Writer) error {
    85  	fmt.Fprintf(stdout, "Building docker image for %s and tagging it as %s:%s\n", buildSpecsWithDeps.Spec.ProductName, image.Repository, image.Tag)
    86  
    87  	contextDir := path.Join(buildSpecsWithDeps.Spec.ProjectDir, image.ContextDir)
    88  
    89  	// link dependent dist artifacts into the context directory
    90  	for depProduct, depTypes := range dockerDepsToMap(image.Deps) {
    91  		for depType, targetFile := range depTypes {
    92  			if !isDist(depType) {
    93  				continue
    94  			}
    95  			if _, ok := specsMap[depProduct]; !ok {
    96  				return errors.Errorf("Unable to find the dependent product %v for %v",
    97  					depProduct, buildSpecsWithDeps.Spec.ProductName)
    98  			}
    99  			depSpec := specsMap[depProduct].Spec
   100  			distsMap := buildDistsMap(specsMap[depProduct].Spec)
   101  			if _, ok := distsMap[string(depType)]; !ok {
   102  				return errors.Errorf("Unable to find dist type %v on the dependent product %v for %v",
   103  					depType, depProduct, buildSpecsWithDeps.Spec.ProductName)
   104  			}
   105  			distCfg := distsMap[string(depType)]
   106  
   107  			for _, artifactLocation := range dist.FullArtifactsPaths(dist.ToDister(distCfg.Info), depSpec, distCfg) {
   108  				if targetFile == "" {
   109  					targetFile = path.Base(artifactLocation)
   110  				}
   111  				target := path.Join(contextDir, targetFile)
   112  				if _, err := os.Stat(target); err == nil {
   113  					// ensure the target does not exists before creating a new one
   114  					if err := os.Remove(target); err != nil {
   115  						return err
   116  					}
   117  				}
   118  				if err := os.Link(artifactLocation, target); err != nil {
   119  					return err
   120  				}
   121  			}
   122  		}
   123  	}
   124  	builder := GetBuilder(image)
   125  	buildWriter := ioutil.Discard
   126  	if verbose {
   127  		buildWriter = stdout
   128  	}
   129  	return builder.build(buildSpecsWithDeps, buildWriter)
   130  }
   131  
   132  func dockerDepsToMap(deps []params.DockerDep) map[string]map[params.DockerDepType]string {
   133  	m := make(map[string]map[params.DockerDepType]string)
   134  	for _, dep := range deps {
   135  		if m[dep.Product] == nil {
   136  			m[dep.Product] = make(map[params.DockerDepType]string)
   137  		}
   138  		m[dep.Product][dep.Type] = dep.TargetFile
   139  	}
   140  	return m
   141  }
   142  
   143  func buildSpecsMap(specs []params.ProductBuildSpecWithDeps) map[string]params.ProductBuildSpecWithDeps {
   144  	specMap := make(map[string]params.ProductBuildSpecWithDeps)
   145  	for _, spec := range specs {
   146  		specMap[spec.Spec.ProductName] = spec
   147  	}
   148  	return specMap
   149  }
   150  
   151  func buildDistsMap(spec params.ProductBuildSpec) map[string]params.Dist {
   152  	distMap := make(map[string]params.Dist)
   153  	for _, dist := range spec.Dist {
   154  		distMap[string(dist.Info.Type())] = dist
   155  	}
   156  	return distMap
   157  }