github.com/nak3/source-to-image@v1.1.10-0.20180319140719-2ed55639898d/pkg/build/strategies/sti/postexecutorstep.go (about)

     1  package sti
     2  
     3  import (
     4  	"archive/tar"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/openshift/source-to-image/pkg/api"
    16  	dockerpkg "github.com/openshift/source-to-image/pkg/docker"
    17  	s2ierr "github.com/openshift/source-to-image/pkg/errors"
    18  	s2itar "github.com/openshift/source-to-image/pkg/tar"
    19  	"github.com/openshift/source-to-image/pkg/util"
    20  	"github.com/openshift/source-to-image/pkg/util/fs"
    21  	utilstatus "github.com/openshift/source-to-image/pkg/util/status"
    22  )
    23  
    24  const maximumLabelSize = 10240
    25  
    26  type postExecutorStepContext struct {
    27  	// id of the previous image that we're holding because after committing the image, we'll lose it.
    28  	// Used only when build is incremental and RemovePreviousImage setting is enabled.
    29  	// See also: storePreviousImageStep and removePreviousImageStep
    30  	previousImageID string
    31  
    32  	// Container id that will be committed.
    33  	// See also: commitImageStep
    34  	containerID string
    35  
    36  	// Path to a directory in the image where scripts (for example, "run") will be placed.
    37  	// This location will be used for generation of the CMD directive.
    38  	// See also: commitImageStep
    39  	destination string
    40  
    41  	// Image id created by committing the container.
    42  	// See also: commitImageStep and reportAboutSuccessStep
    43  	imageID string
    44  
    45  	// Labels that will be passed to a callback.
    46  	// These labels are added to the image during commit.
    47  	// See also: commitImageStep and STI.Build()
    48  	labels map[string]string
    49  }
    50  
    51  type postExecutorStep interface {
    52  	execute(*postExecutorStepContext) error
    53  }
    54  
    55  type storePreviousImageStep struct {
    56  	builder *STI
    57  	docker  dockerpkg.Docker
    58  }
    59  
    60  func (step *storePreviousImageStep) execute(ctx *postExecutorStepContext) error {
    61  	if step.builder.incremental && step.builder.config.RemovePreviousImage {
    62  		glog.V(3).Info("Executing step: store previous image")
    63  		ctx.previousImageID = step.getPreviousImage()
    64  		return nil
    65  	}
    66  
    67  	glog.V(3).Info("Skipping step: store previous image")
    68  	return nil
    69  }
    70  
    71  func (step *storePreviousImageStep) getPreviousImage() string {
    72  	previousImageID, err := step.docker.GetImageID(step.builder.config.Tag)
    73  	if err != nil {
    74  		glog.V(0).Infof("error: Error retrieving previous image's (%v) metadata: %v", step.builder.config.Tag, err)
    75  		return ""
    76  	}
    77  	return previousImageID
    78  }
    79  
    80  type removePreviousImageStep struct {
    81  	builder *STI
    82  	docker  dockerpkg.Docker
    83  }
    84  
    85  func (step *removePreviousImageStep) execute(ctx *postExecutorStepContext) error {
    86  	if step.builder.incremental && step.builder.config.RemovePreviousImage {
    87  		glog.V(3).Info("Executing step: remove previous image")
    88  		step.removePreviousImage(ctx.previousImageID)
    89  		return nil
    90  	}
    91  
    92  	glog.V(3).Info("Skipping step: remove previous image")
    93  	return nil
    94  }
    95  
    96  func (step *removePreviousImageStep) removePreviousImage(previousImageID string) {
    97  	if previousImageID == "" {
    98  		return
    99  	}
   100  
   101  	glog.V(1).Infof("Removing previously-tagged image %s", previousImageID)
   102  	if err := step.docker.RemoveImage(previousImageID); err != nil {
   103  		glog.V(0).Infof("error: Unable to remove previous image: %v", err)
   104  	}
   105  }
   106  
   107  type commitImageStep struct {
   108  	image   string
   109  	builder *STI
   110  	docker  dockerpkg.Docker
   111  	fs      fs.FileSystem
   112  	tar     s2itar.Tar
   113  }
   114  
   115  func (step *commitImageStep) execute(ctx *postExecutorStepContext) error {
   116  	glog.V(3).Infof("Executing step: commit image")
   117  
   118  	user, err := step.docker.GetImageUser(step.image)
   119  	if err != nil {
   120  		return fmt.Errorf("could not get user of %q image: %v", step.image, err)
   121  	}
   122  
   123  	cmd := createCommandForExecutingRunScript(step.builder.scriptsURL, ctx.destination)
   124  
   125  	if err = checkAndGetNewLabels(step.builder, step.docker, step.tar, ctx.containerID); err != nil {
   126  		return fmt.Errorf("could not check for new labels for %q image: %v", step.image, err)
   127  	}
   128  
   129  	ctx.labels = createLabelsForResultingImage(step.builder, step.docker, step.image)
   130  
   131  	if err = checkLabelSize(ctx.labels); err != nil {
   132  		return fmt.Errorf("label validation failed for %q image: %v", step.image, err)
   133  	}
   134  
   135  	// Set the image entrypoint back to its original value on commit, the running
   136  	// container has "env" as its entrypoint and we don't want to commit that.
   137  	entrypoint, err := step.docker.GetImageEntrypoint(step.image)
   138  	if err != nil {
   139  		return fmt.Errorf("could not get entrypoint of %q image: %v", step.image, err)
   140  	}
   141  	// If the image has no explicit entrypoint, set it to an empty array
   142  	// so we don't default to leaving the entrypoint as "env" upon commit.
   143  	if entrypoint == nil {
   144  		entrypoint = []string{}
   145  	}
   146  	startTime := time.Now()
   147  	ctx.imageID, err = commitContainer(
   148  		step.docker,
   149  		ctx.containerID,
   150  		cmd,
   151  		user,
   152  		step.builder.config.Tag,
   153  		step.builder.env,
   154  		entrypoint,
   155  		ctx.labels,
   156  	)
   157  	step.builder.result.BuildInfo.Stages = api.RecordStageAndStepInfo(step.builder.result.BuildInfo.Stages, api.StageCommit, api.StepCommitContainer, startTime, time.Now())
   158  	if err != nil {
   159  		step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   160  			utilstatus.ReasonCommitContainerFailed,
   161  			utilstatus.ReasonMessageCommitContainerFailed,
   162  		)
   163  		return err
   164  	}
   165  
   166  	return nil
   167  }
   168  
   169  type downloadFilesFromBuilderImageStep struct {
   170  	builder *STI
   171  	docker  dockerpkg.Docker
   172  	fs      fs.FileSystem
   173  	tar     s2itar.Tar
   174  }
   175  
   176  func (step *downloadFilesFromBuilderImageStep) execute(ctx *postExecutorStepContext) error {
   177  	glog.V(3).Info("Executing step: download files from the builder image")
   178  
   179  	artifactsDir := filepath.Join(step.builder.config.WorkingDir, api.RuntimeArtifactsDir)
   180  	if err := step.fs.Mkdir(artifactsDir); err != nil {
   181  		step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   182  			utilstatus.ReasonFSOperationFailed,
   183  			utilstatus.ReasonMessageFSOperationFailed,
   184  		)
   185  		return fmt.Errorf("could not create directory %q: %v", artifactsDir, err)
   186  	}
   187  
   188  	for _, artifact := range step.builder.config.RuntimeArtifacts {
   189  		if err := step.downloadAndExtractFile(artifact.Source, artifactsDir, ctx.containerID); err != nil {
   190  			step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   191  				utilstatus.ReasonRuntimeArtifactsFetchFailed,
   192  				utilstatus.ReasonMessageRuntimeArtifactsFetchFailed,
   193  			)
   194  			return err
   195  		}
   196  
   197  		// for mapping like "/tmp/foo.txt -> app" we should create "app" and move "foo.txt" to that directory
   198  		dstSubDir := path.Clean(artifact.Destination)
   199  		if dstSubDir != "." && dstSubDir != "/" {
   200  			dstDir := filepath.Join(artifactsDir, dstSubDir)
   201  			glog.V(5).Infof("Creating directory %q", dstDir)
   202  			if err := step.fs.MkdirAll(dstDir); err != nil {
   203  				step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   204  					utilstatus.ReasonFSOperationFailed,
   205  					utilstatus.ReasonMessageFSOperationFailed,
   206  				)
   207  				return fmt.Errorf("could not create directory %q: %v", dstDir, err)
   208  			}
   209  
   210  			currentFile := filepath.Base(artifact.Source)
   211  			oldFile := filepath.Join(artifactsDir, currentFile)
   212  			newFile := filepath.Join(artifactsDir, dstSubDir, currentFile)
   213  			glog.V(5).Infof("Renaming %q to %q", oldFile, newFile)
   214  			if err := step.fs.Rename(oldFile, newFile); err != nil {
   215  				step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   216  					utilstatus.ReasonFSOperationFailed,
   217  					utilstatus.ReasonMessageFSOperationFailed,
   218  				)
   219  				return fmt.Errorf("could not rename %q -> %q: %v", oldFile, newFile, err)
   220  			}
   221  		}
   222  	}
   223  
   224  	return nil
   225  }
   226  
   227  func (step *downloadFilesFromBuilderImageStep) downloadAndExtractFile(artifactPath, artifactsDir, containerID string) error {
   228  	if res, err := downloadAndExtractFileFromContainer(step.docker, step.tar, artifactPath, artifactsDir, containerID); err != nil {
   229  		step.builder.result.BuildInfo.FailureReason = res
   230  		return err
   231  	}
   232  	return nil
   233  }
   234  
   235  type startRuntimeImageAndUploadFilesStep struct {
   236  	builder *STI
   237  	docker  dockerpkg.Docker
   238  	fs      fs.FileSystem
   239  }
   240  
   241  func (step *startRuntimeImageAndUploadFilesStep) execute(ctx *postExecutorStepContext) error {
   242  	glog.V(3).Info("Executing step: start runtime image and upload files")
   243  
   244  	fd, err := ioutil.TempFile("", "s2i-upload-done")
   245  	if err != nil {
   246  		step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   247  			utilstatus.ReasonGenericS2IBuildFailed,
   248  			utilstatus.ReasonMessageGenericS2iBuildFailed,
   249  		)
   250  		return err
   251  	}
   252  	fd.Close()
   253  	lastFilePath := fd.Name()
   254  	defer func() {
   255  		os.Remove(lastFilePath)
   256  	}()
   257  
   258  	lastFileDstPath := "/tmp/" + filepath.Base(lastFilePath)
   259  
   260  	outReader, outWriter := io.Pipe()
   261  	errReader, errWriter := io.Pipe()
   262  
   263  	artifactsDir := filepath.Join(step.builder.config.WorkingDir, api.RuntimeArtifactsDir)
   264  
   265  	// We copy scripts to a directory with artifacts to upload files in one shot
   266  	for _, script := range []string{api.AssembleRuntime, api.Run} {
   267  		// scripts must be inside of "scripts" subdir, see createCommandForExecutingRunScript()
   268  		destinationDir := filepath.Join(artifactsDir, "scripts")
   269  		err = step.copyScriptIfNeeded(script, destinationDir)
   270  		if err != nil {
   271  			step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   272  				utilstatus.ReasonGenericS2IBuildFailed,
   273  				utilstatus.ReasonMessageGenericS2iBuildFailed,
   274  			)
   275  			return err
   276  		}
   277  	}
   278  
   279  	image := step.builder.config.RuntimeImage
   280  	workDir, err := step.docker.GetImageWorkdir(image)
   281  	if err != nil {
   282  		step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   283  			utilstatus.ReasonGenericS2IBuildFailed,
   284  			utilstatus.ReasonMessageGenericS2iBuildFailed,
   285  		)
   286  		return fmt.Errorf("could not get working dir of %q image: %v", image, err)
   287  	}
   288  
   289  	commandBaseDir := filepath.Join(workDir, "scripts")
   290  	useExternalAssembleScript := step.builder.externalScripts[api.AssembleRuntime]
   291  	if !useExternalAssembleScript {
   292  		// script already inside of the image
   293  		var scriptsURL string
   294  		scriptsURL, err = step.docker.GetScriptsURL(image)
   295  		if err != nil {
   296  			step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   297  				utilstatus.ReasonGenericS2IBuildFailed,
   298  				utilstatus.ReasonMessageGenericS2iBuildFailed,
   299  			)
   300  			return err
   301  		}
   302  		if len(scriptsURL) == 0 {
   303  			step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   304  				utilstatus.ReasonGenericS2IBuildFailed,
   305  				utilstatus.ReasonMessageGenericS2iBuildFailed,
   306  			)
   307  			return fmt.Errorf("could not determine scripts URL for image %q", image)
   308  		}
   309  		commandBaseDir = strings.TrimPrefix(scriptsURL, "image://")
   310  	}
   311  
   312  	cmd := fmt.Sprintf(
   313  		"while [ ! -f %q ]; do sleep 0.5; done; %s/%s; exit $?",
   314  		lastFileDstPath,
   315  		commandBaseDir,
   316  		api.AssembleRuntime,
   317  	)
   318  
   319  	opts := dockerpkg.RunContainerOptions{
   320  		Image:           image,
   321  		PullImage:       false, // The PullImage is false because we've already pulled the image
   322  		CommandExplicit: []string{"/bin/sh", "-c", cmd},
   323  		Stdout:          outWriter,
   324  		Stderr:          errWriter,
   325  		NetworkMode:     string(step.builder.config.DockerNetworkMode),
   326  		CGroupLimits:    step.builder.config.CGroupLimits,
   327  		CapDrop:         step.builder.config.DropCapabilities,
   328  		PostExec:        step.builder.postExecutor,
   329  		Env:             step.builder.env,
   330  	}
   331  
   332  	opts.OnStart = func(containerID string) error {
   333  		setStandardPerms := func(writer io.Writer) s2itar.Writer {
   334  			return s2itar.ChmodAdapter{Writer: tar.NewWriter(writer), NewFileMode: 0644, NewExecFileMode: 0755, NewDirMode: 0755}
   335  		}
   336  
   337  		glog.V(5).Infof("Uploading directory %q -> %q", artifactsDir, workDir)
   338  		onStartErr := step.docker.UploadToContainerWithTarWriter(step.fs, artifactsDir, workDir, containerID, setStandardPerms)
   339  		if onStartErr != nil {
   340  			return fmt.Errorf("could not upload directory (%q -> %q) into container %s: %v", artifactsDir, workDir, containerID, err)
   341  		}
   342  
   343  		glog.V(5).Infof("Uploading file %q -> %q", lastFilePath, lastFileDstPath)
   344  		onStartErr = step.docker.UploadToContainerWithTarWriter(step.fs, lastFilePath, lastFileDstPath, containerID, setStandardPerms)
   345  		if onStartErr != nil {
   346  			return fmt.Errorf("could not upload file (%q -> %q) into container %s: %v", lastFilePath, lastFileDstPath, containerID, err)
   347  		}
   348  
   349  		return onStartErr
   350  	}
   351  
   352  	dockerpkg.StreamContainerIO(outReader, nil, func(s string) { glog.V(0).Info(s) })
   353  
   354  	errOutput := ""
   355  	c := dockerpkg.StreamContainerIO(errReader, &errOutput, func(s string) { glog.Info(s) })
   356  
   357  	// switch to the next stage of post executors steps
   358  	step.builder.postExecutorStage++
   359  
   360  	err = step.docker.RunContainer(opts)
   361  	if e, ok := err.(s2ierr.ContainerError); ok {
   362  		// Must wait for StreamContainerIO goroutine above to exit before reading errOutput.
   363  		<-c
   364  		err = s2ierr.NewContainerError(image, e.ErrorCode, errOutput+e.Output)
   365  	}
   366  
   367  	return err
   368  }
   369  
   370  func (step *startRuntimeImageAndUploadFilesStep) copyScriptIfNeeded(script, destinationDir string) error {
   371  	useExternalScript := step.builder.externalScripts[script]
   372  	if useExternalScript {
   373  		src := filepath.Join(step.builder.config.WorkingDir, api.UploadScripts, script)
   374  		dst := filepath.Join(destinationDir, script)
   375  		glog.V(5).Infof("Copying file %q -> %q", src, dst)
   376  		if err := step.fs.MkdirAll(destinationDir); err != nil {
   377  			return fmt.Errorf("could not create directory %q: %v", destinationDir, err)
   378  		}
   379  		if err := step.fs.Copy(src, dst); err != nil {
   380  			return fmt.Errorf("could not copy file (%q -> %q): %v", src, dst, err)
   381  		}
   382  	}
   383  	return nil
   384  }
   385  
   386  type reportSuccessStep struct {
   387  	builder *STI
   388  }
   389  
   390  func (step *reportSuccessStep) execute(ctx *postExecutorStepContext) error {
   391  	glog.V(3).Info("Executing step: report success")
   392  
   393  	step.builder.result.Success = true
   394  	step.builder.result.ImageID = ctx.imageID
   395  
   396  	glog.V(3).Infof("Successfully built %s", firstNonEmpty(step.builder.config.Tag, ctx.imageID))
   397  
   398  	return nil
   399  }
   400  
   401  // shared methods
   402  
   403  func commitContainer(docker dockerpkg.Docker, containerID, cmd, user, tag string, env, entrypoint []string, labels map[string]string) (string, error) {
   404  	opts := dockerpkg.CommitContainerOptions{
   405  		Command:     []string{cmd},
   406  		Env:         env,
   407  		Entrypoint:  entrypoint,
   408  		ContainerID: containerID,
   409  		Repository:  tag,
   410  		User:        user,
   411  		Labels:      labels,
   412  	}
   413  
   414  	imageID, err := docker.CommitContainer(opts)
   415  	if err != nil {
   416  		return "", s2ierr.NewCommitError(tag, err)
   417  	}
   418  
   419  	return imageID, nil
   420  }
   421  
   422  func createLabelsForResultingImage(builder *STI, docker dockerpkg.Docker, baseImage string) map[string]string {
   423  	generatedLabels := util.GenerateOutputImageLabels(builder.sourceInfo, builder.config)
   424  
   425  	existingLabels, err := docker.GetLabels(baseImage)
   426  	if err != nil {
   427  		glog.V(0).Infof("error: Unable to read existing labels from the base image %s", baseImage)
   428  	}
   429  
   430  	configLabels := builder.config.Labels
   431  	newLabels := builder.newLabels
   432  
   433  	return mergeLabels(existingLabels, generatedLabels, configLabels, newLabels)
   434  }
   435  
   436  func mergeLabels(labels ...map[string]string) map[string]string {
   437  	mergedLabels := map[string]string{}
   438  
   439  	for _, labelMap := range labels {
   440  		for k, v := range labelMap {
   441  			mergedLabels[k] = v
   442  		}
   443  	}
   444  	return mergedLabels
   445  }
   446  
   447  func createCommandForExecutingRunScript(scriptsURL map[string]string, location string) string {
   448  	cmd := scriptsURL[api.Run]
   449  	if strings.HasPrefix(cmd, "image://") {
   450  		// scripts from inside of the image, we need to strip the image part
   451  		// NOTE: We use path.Join instead of filepath.Join to avoid converting the
   452  		// path to UNC (Windows) format as we always run this inside container.
   453  		cmd = strings.TrimPrefix(cmd, "image://")
   454  	} else {
   455  		// external scripts, in which case we're taking the directory to which they
   456  		// were extracted and append scripts dir and name
   457  		cmd = path.Join(location, "scripts", api.Run)
   458  	}
   459  	return cmd
   460  }
   461  
   462  func downloadAndExtractFileFromContainer(docker dockerpkg.Docker, tar s2itar.Tar, sourcePath, destinationPath, containerID string) (api.FailureReason, error) {
   463  	glog.V(5).Infof("Downloading file %q", sourcePath)
   464  
   465  	fd, err := ioutil.TempFile(destinationPath, "s2i-runtime-artifact")
   466  	if err != nil {
   467  		res := utilstatus.NewFailureReason(
   468  			utilstatus.ReasonFSOperationFailed,
   469  			utilstatus.ReasonMessageFSOperationFailed,
   470  		)
   471  		return res, fmt.Errorf("could not create temporary file for runtime artifact: %v", err)
   472  	}
   473  	defer func() {
   474  		fd.Close()
   475  		os.Remove(fd.Name())
   476  	}()
   477  
   478  	if err := docker.DownloadFromContainer(sourcePath, fd, containerID); err != nil {
   479  		res := utilstatus.NewFailureReason(
   480  			utilstatus.ReasonGenericS2IBuildFailed,
   481  			utilstatus.ReasonMessageGenericS2iBuildFailed,
   482  		)
   483  		return res, fmt.Errorf("could not download file (%q -> %q) from container %s: %v", sourcePath, fd.Name(), containerID, err)
   484  	}
   485  
   486  	// after writing to the file descriptor we need to rewind pointer to the beginning of the file before next reading
   487  	if _, err := fd.Seek(0, io.SeekStart); err != nil {
   488  		res := utilstatus.NewFailureReason(
   489  			utilstatus.ReasonGenericS2IBuildFailed,
   490  			utilstatus.ReasonMessageGenericS2iBuildFailed,
   491  		)
   492  		return res, fmt.Errorf("could not seek to the beginning of the file %q: %v", fd.Name(), err)
   493  	}
   494  
   495  	if err := tar.ExtractTarStream(destinationPath, fd); err != nil {
   496  		res := utilstatus.NewFailureReason(
   497  			utilstatus.ReasonGenericS2IBuildFailed,
   498  			utilstatus.ReasonMessageGenericS2iBuildFailed,
   499  		)
   500  		return res, fmt.Errorf("could not extract artifact %q into the directory %q: %v", sourcePath, destinationPath, err)
   501  	}
   502  
   503  	return utilstatus.NewFailureReason("", ""), nil
   504  }
   505  
   506  func checkLabelSize(labels map[string]string) error {
   507  	var sum = 0
   508  	for k, v := range labels {
   509  		sum += len(k) + len(v)
   510  	}
   511  
   512  	if sum > maximumLabelSize {
   513  		return fmt.Errorf("label size '%d' exceeds the maximum limit '%d'", sum, maximumLabelSize)
   514  	}
   515  
   516  	return nil
   517  }
   518  
   519  // check for new labels and apply to the output image.
   520  func checkAndGetNewLabels(builder *STI, docker dockerpkg.Docker, tar s2itar.Tar, containerID string) error {
   521  	glog.V(3).Infof("Checking for new Labels to apply... ")
   522  
   523  	// metadata filename and its path inside the container
   524  	metadataFilename := "image_metadata.json"
   525  	sourceFilepath := filepath.Join("/tmp/.s2i", metadataFilename)
   526  
   527  	// create the 'downloadPath' folder if it doesn't exist
   528  	downloadPath := filepath.Join(builder.config.WorkingDir, "metadata")
   529  	glog.V(3).Infof("Creating the download path '%s'", downloadPath)
   530  	if err := os.MkdirAll(downloadPath, 0700); err != nil {
   531  		glog.Errorf("Error creating dir %q for '%s': %v", downloadPath, metadataFilename, err)
   532  		return err
   533  	}
   534  
   535  	// download & extract the file from container
   536  	if _, err := downloadAndExtractFileFromContainer(docker, tar, sourceFilepath, downloadPath, containerID); err != nil {
   537  		glog.V(3).Infof("unable to download and extract '%s' ... continuing", metadataFilename)
   538  		return nil
   539  	}
   540  
   541  	// open the file
   542  	filePath := filepath.Join(downloadPath, metadataFilename)
   543  	fd, err := os.Open(filePath)
   544  	if fd == nil || err != nil {
   545  		return fmt.Errorf("unable to open file '%s' : %v", downloadPath, err)
   546  	}
   547  	defer fd.Close()
   548  
   549  	// read the file to a string
   550  	str, err := ioutil.ReadAll(fd)
   551  	if err != nil {
   552  		return fmt.Errorf("error reading file '%s' in to a string: %v", filePath, err)
   553  	}
   554  	glog.V(3).Infof("new Labels File contents : \n%s\n", str)
   555  
   556  	// string into a map
   557  	var data map[string]interface{}
   558  
   559  	if err = json.Unmarshal([]byte(str), &data); err != nil {
   560  		return fmt.Errorf("JSON Unmarshal Error with '%s' file : %v", metadataFilename, err)
   561  	}
   562  
   563  	// update newLabels[]
   564  	labels := data["labels"]
   565  	for _, l := range labels.([]interface{}) {
   566  		for k, v := range l.(map[string]interface{}) {
   567  			builder.newLabels[k] = v.(string)
   568  		}
   569  	}
   570  
   571  	return nil
   572  }