kubesphere.io/s2irun@v3.2.1+incompatible/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/kubesphere/s2irun/pkg/api"
    16  	"github.com/kubesphere/s2irun/pkg/api/constants"
    17  	dockerpkg "github.com/kubesphere/s2irun/pkg/docker"
    18  	s2ierr "github.com/kubesphere/s2irun/pkg/errors"
    19  	s2itar "github.com/kubesphere/s2irun/pkg/tar"
    20  	"github.com/kubesphere/s2irun/pkg/utils"
    21  	"github.com/kubesphere/s2irun/pkg/utils/fs"
    22  	utilstatus "github.com/kubesphere/s2irun/pkg/utils/status"
    23  )
    24  
    25  const maximumLabelSize = 10240
    26  
    27  type postExecutorStepContext struct {
    28  	// id of the previous image that we're holding because after committing the image, we'll lose it.
    29  	// Used only when build is incremental and RemovePreviousImage setting is enabled.
    30  	// See also: storePreviousImageStep and removePreviousImageStep
    31  	previousImageID string
    32  
    33  	// Container id that will be committed.
    34  	// See also: commitImageStep
    35  	containerID string
    36  
    37  	// Path to a directory in the image where scripts (for example, "run") will be placed.
    38  	// This location will be used for generation of the CMD directive.
    39  	// See also: commitImageStep
    40  	destination string
    41  
    42  	// Image id created by committing the container.
    43  	// See also: commitImageStep and reportAboutSuccessStep
    44  	imageID string
    45  
    46  	// Labels that will be passed to a callback.
    47  	// These labels are added to the image during commit.
    48  	// See also: commitImageStep and STI.Build()
    49  	labels map[string]string
    50  }
    51  
    52  type postExecutorStep interface {
    53  	execute(*postExecutorStepContext) error
    54  }
    55  
    56  type storePreviousImageStep struct {
    57  	builder *STI
    58  	docker  dockerpkg.Docker
    59  }
    60  
    61  func (step *storePreviousImageStep) execute(ctx *postExecutorStepContext) error {
    62  	if step.builder.incremental && step.builder.config.RemovePreviousImage {
    63  		glog.V(3).Info("Executing step: store previous image")
    64  		ctx.previousImageID = step.getPreviousImage()
    65  		return nil
    66  	}
    67  
    68  	glog.V(3).Info("Skipping step: store previous image")
    69  	return nil
    70  }
    71  
    72  func (step *storePreviousImageStep) getPreviousImage() string {
    73  	previousImageID, err := step.docker.GetImageID(step.builder.config.Tag)
    74  	if err != nil {
    75  		glog.V(0).Infof("error: Error retrieving previous image's (%v) metadata: %v", step.builder.config.Tag, err)
    76  		return ""
    77  	}
    78  	return previousImageID
    79  }
    80  
    81  type removePreviousImageStep struct {
    82  	builder *STI
    83  	docker  dockerpkg.Docker
    84  }
    85  
    86  func (step *removePreviousImageStep) execute(ctx *postExecutorStepContext) error {
    87  	if step.builder.incremental && step.builder.config.RemovePreviousImage {
    88  		glog.V(3).Info("Executing step: remove previous image")
    89  		step.removePreviousImage(ctx.previousImageID)
    90  		return nil
    91  	}
    92  
    93  	glog.V(3).Info("Skipping step: remove previous image")
    94  	return nil
    95  }
    96  
    97  func (step *removePreviousImageStep) removePreviousImage(previousImageID string) {
    98  	if previousImageID == "" {
    99  		return
   100  	}
   101  
   102  	glog.V(1).Infof("Removing previously-tagged image %s", previousImageID)
   103  	if err := step.docker.RemoveImage(previousImageID); err != nil {
   104  		glog.V(0).Infof("error: Unable to remove previous image: %v", err)
   105  	}
   106  }
   107  
   108  type commitImageStep struct {
   109  	image   string
   110  	builder *STI
   111  	docker  dockerpkg.Docker
   112  	fs      fs.FileSystem
   113  	tar     s2itar.Tar
   114  }
   115  
   116  func (step *commitImageStep) execute(ctx *postExecutorStepContext) error {
   117  	glog.V(3).Infof("Executing step: commit image")
   118  
   119  	user, err := step.docker.GetImageUser(step.image)
   120  	if err != nil {
   121  		return fmt.Errorf("could not get user of %q image: %v", step.image, err)
   122  	}
   123  
   124  	cmd := createCommandForExecutingRunScript(step.builder.scriptsURL, ctx.destination)
   125  
   126  	if err = checkAndGetNewLabels(step.builder, step.docker, step.tar, ctx.containerID); err != nil {
   127  		return fmt.Errorf("could not check for new labels for %q image: %v", step.image, err)
   128  	}
   129  
   130  	ctx.labels = createLabelsForResultingImage(step.builder, step.docker, step.image)
   131  
   132  	if err = checkLabelSize(ctx.labels); err != nil {
   133  		return fmt.Errorf("label validation failed for %q image: %v", step.image, err)
   134  	}
   135  
   136  	// Set the image entrypoint back to its original value on commit, the running
   137  	// container has "env" as its entrypoint and we don't want to commit that.
   138  	entrypoint, err := step.docker.GetImageEntrypoint(step.image)
   139  	if err != nil {
   140  		return fmt.Errorf("could not get entrypoint of %q image: %v", step.image, err)
   141  	}
   142  	// If the image has no explicit entrypoint, set it to an empty array
   143  	// so we don't default to leaving the entrypoint as "env" upon commit.
   144  	if entrypoint == nil {
   145  		entrypoint = []string{}
   146  	}
   147  	startTime := time.Now()
   148  	ctx.imageID, err = commitContainer(
   149  		step.docker,
   150  		ctx.containerID,
   151  		cmd,
   152  		user,
   153  		step.builder.config.Tag,
   154  		step.builder.env,
   155  		entrypoint,
   156  		ctx.labels,
   157  	)
   158  	step.builder.result.BuildInfo.Stages = api.RecordStageAndStepInfo(step.builder.result.BuildInfo.Stages, api.StageCommit, api.StepCommitContainer, startTime, time.Now())
   159  	if err != nil {
   160  		step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   161  			utilstatus.ReasonCommitContainerFailed,
   162  			utilstatus.ReasonMessageCommitContainerFailed,
   163  		)
   164  		return err
   165  	}
   166  
   167  	return nil
   168  }
   169  
   170  type downloadFilesFromBuilderImageStep struct {
   171  	builder *STI
   172  	docker  dockerpkg.Docker
   173  	fs      fs.FileSystem
   174  	tar     s2itar.Tar
   175  }
   176  
   177  func (step *downloadFilesFromBuilderImageStep) execute(ctx *postExecutorStepContext) error {
   178  	glog.V(3).Info("Executing step: download files from the builder image")
   179  
   180  	artifactsDir := filepath.Join(step.builder.config.WorkingDir, constants.RuntimeArtifactsDir)
   181  	if err := step.fs.Mkdir(artifactsDir); err != nil {
   182  		step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   183  			utilstatus.ReasonFSOperationFailed,
   184  			utilstatus.ReasonMessageFSOperationFailed,
   185  		)
   186  		return fmt.Errorf("could not create directory %q: %v", artifactsDir, err)
   187  	}
   188  
   189  	for _, artifact := range step.builder.config.RuntimeArtifacts {
   190  		if err := step.downloadAndExtractFile(artifact.Source, artifactsDir, ctx.containerID); err != nil {
   191  			step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   192  				utilstatus.ReasonRuntimeArtifactsFetchFailed,
   193  				utilstatus.ReasonMessageRuntimeArtifactsFetchFailed,
   194  			)
   195  			return err
   196  		}
   197  
   198  		// for mapping like "/tmp/foo.txt -> app" we should create "app" and move "foo.txt" to that directory
   199  		dstSubDir := path.Clean(artifact.Destination)
   200  		if dstSubDir != "." && dstSubDir != "/" {
   201  			dstDir := filepath.Join(artifactsDir, dstSubDir)
   202  			glog.V(5).Infof("Creating directory %q", dstDir)
   203  			if err := step.fs.MkdirAll(dstDir); err != nil {
   204  				step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   205  					utilstatus.ReasonFSOperationFailed,
   206  					utilstatus.ReasonMessageFSOperationFailed,
   207  				)
   208  				return fmt.Errorf("could not create directory %q: %v", dstDir, err)
   209  			}
   210  
   211  			currentFile := filepath.Base(artifact.Source)
   212  			oldFile := filepath.Join(artifactsDir, currentFile)
   213  			newFile := filepath.Join(artifactsDir, dstSubDir, currentFile)
   214  			glog.V(5).Infof("Renaming %q to %q", oldFile, newFile)
   215  			if err := step.fs.Rename(oldFile, newFile); err != nil {
   216  				step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   217  					utilstatus.ReasonFSOperationFailed,
   218  					utilstatus.ReasonMessageFSOperationFailed,
   219  				)
   220  				return fmt.Errorf("could not rename %q -> %q: %v", oldFile, newFile, err)
   221  			}
   222  		}
   223  	}
   224  
   225  	return nil
   226  }
   227  
   228  func (step *downloadFilesFromBuilderImageStep) downloadAndExtractFile(artifactPath, artifactsDir, containerID string) error {
   229  	if res, err := downloadAndExtractFileFromContainer(step.docker, step.tar, artifactPath, artifactsDir, containerID); err != nil {
   230  		step.builder.result.BuildInfo.FailureReason = res
   231  		return err
   232  	}
   233  	return nil
   234  }
   235  
   236  type startRuntimeImageAndUploadFilesStep struct {
   237  	builder *STI
   238  	docker  dockerpkg.Docker
   239  	fs      fs.FileSystem
   240  }
   241  
   242  func (step *startRuntimeImageAndUploadFilesStep) execute(ctx *postExecutorStepContext) error {
   243  	glog.V(3).Info("Executing step: start runtime image and upload files")
   244  
   245  	fd, err := ioutil.TempFile("", "s2i-upload-done")
   246  	if err != nil {
   247  		step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   248  			utilstatus.ReasonGenericS2IBuildFailed,
   249  			utilstatus.ReasonMessageGenericS2iBuildFailed,
   250  		)
   251  		return err
   252  	}
   253  	fd.Close()
   254  	lastFilePath := fd.Name()
   255  	defer func() {
   256  		os.Remove(lastFilePath)
   257  	}()
   258  
   259  	lastFileDstPath := "/tmp/" + filepath.Base(lastFilePath)
   260  
   261  	outReader, outWriter := io.Pipe()
   262  	errReader, errWriter := io.Pipe()
   263  
   264  	artifactsDir := filepath.Join(step.builder.config.WorkingDir, constants.RuntimeArtifactsDir)
   265  
   266  	// We copy scripts to a directory with artifacts to upload files in one shot
   267  	for _, script := range []string{constants.AssembleRuntime, constants.Run} {
   268  		// scripts must be inside of "scripts" subdir, see createCommandForExecutingRunScript()
   269  		destinationDir := filepath.Join(artifactsDir, "scripts")
   270  		err = step.copyScriptIfNeeded(script, destinationDir)
   271  		if err != nil {
   272  			step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   273  				utilstatus.ReasonGenericS2IBuildFailed,
   274  				utilstatus.ReasonMessageGenericS2iBuildFailed,
   275  			)
   276  			return err
   277  		}
   278  	}
   279  
   280  	image := step.builder.config.RuntimeImage
   281  	workDir, err := step.docker.GetImageWorkdir(image)
   282  	if err != nil {
   283  		step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   284  			utilstatus.ReasonGenericS2IBuildFailed,
   285  			utilstatus.ReasonMessageGenericS2iBuildFailed,
   286  		)
   287  		return fmt.Errorf("could not get working dir of %q image: %v", image, err)
   288  	}
   289  
   290  	commandBaseDir := filepath.Join(workDir, "scripts")
   291  	useExternalAssembleScript := step.builder.externalScripts[constants.AssembleRuntime]
   292  	if !useExternalAssembleScript {
   293  		// script already inside of the image
   294  		var scriptsURL string
   295  		scriptsURL, err = step.docker.GetScriptsURL(image)
   296  		if err != nil {
   297  			step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   298  				utilstatus.ReasonGenericS2IBuildFailed,
   299  				utilstatus.ReasonMessageGenericS2iBuildFailed,
   300  			)
   301  			return err
   302  		}
   303  		if len(scriptsURL) == 0 {
   304  			step.builder.result.BuildInfo.FailureReason = utilstatus.NewFailureReason(
   305  				utilstatus.ReasonGenericS2IBuildFailed,
   306  				utilstatus.ReasonMessageGenericS2iBuildFailed,
   307  			)
   308  			return fmt.Errorf("could not determine scripts URL for image %q", image)
   309  		}
   310  		commandBaseDir = strings.TrimPrefix(scriptsURL, "image://")
   311  	}
   312  
   313  	cmd := fmt.Sprintf(
   314  		"while [ ! -f %q ]; do sleep 0.5; done; %s/%s; exit $?",
   315  		lastFileDstPath,
   316  		commandBaseDir,
   317  		constants.AssembleRuntime,
   318  	)
   319  
   320  	opts := dockerpkg.RunContainerOptions{
   321  		Image:           image,
   322  		PullImage:       false, // The PullImage is false because we've already pulled the image
   323  		CommandExplicit: []string{"/bin/sh", "-c", cmd},
   324  		Stdout:          outWriter,
   325  		Stderr:          errWriter,
   326  		NetworkMode:     string(step.builder.config.DockerNetworkMode),
   327  		CGroupLimits:    step.builder.config.CGroupLimits,
   328  		CapDrop:         step.builder.config.DropCapabilities,
   329  		PostExec:        step.builder.postExecutor,
   330  		Env:             step.builder.env,
   331  	}
   332  
   333  	opts.OnStart = func(containerID string) error {
   334  		setStandardPerms := func(writer io.Writer) s2itar.Writer {
   335  			return s2itar.ChmodAdapter{Writer: tar.NewWriter(writer), NewFileMode: 0644, NewExecFileMode: 0755, NewDirMode: 0755}
   336  		}
   337  
   338  		glog.V(5).Infof("Uploading directory %q -> %q", artifactsDir, workDir)
   339  		onStartErr := step.docker.UploadToContainerWithTarWriter(step.fs, artifactsDir, workDir, containerID, setStandardPerms)
   340  		if onStartErr != nil {
   341  			return fmt.Errorf("could not upload directory (%q -> %q) into container %s: %v", artifactsDir, workDir, containerID, err)
   342  		}
   343  
   344  		glog.V(5).Infof("Uploading file %q -> %q", lastFilePath, lastFileDstPath)
   345  		onStartErr = step.docker.UploadToContainerWithTarWriter(step.fs, lastFilePath, lastFileDstPath, containerID, setStandardPerms)
   346  		if onStartErr != nil {
   347  			return fmt.Errorf("could not upload file (%q -> %q) into container %s: %v", lastFilePath, lastFileDstPath, containerID, err)
   348  		}
   349  
   350  		return onStartErr
   351  	}
   352  
   353  	dockerpkg.StreamContainerIO(outReader, nil, func(s string) { glog.V(0).Info(s) })
   354  
   355  	errOutput := ""
   356  	c := dockerpkg.StreamContainerIO(errReader, &errOutput, func(s string) { glog.Info(s) })
   357  
   358  	// switch to the next stage of post executors steps
   359  	step.builder.postExecutorStage++
   360  
   361  	err = step.docker.RunContainer(opts)
   362  	if e, ok := err.(s2ierr.ContainerError); ok {
   363  		// Must wait for StreamContainerIO goroutine above to exit before reading errOutput.
   364  		<-c
   365  		err = s2ierr.NewContainerError(image, e.ErrorCode, errOutput+e.Output)
   366  	}
   367  
   368  	return err
   369  }
   370  
   371  func (step *startRuntimeImageAndUploadFilesStep) copyScriptIfNeeded(script, destinationDir string) error {
   372  	useExternalScript := step.builder.externalScripts[script]
   373  	if useExternalScript {
   374  		src := filepath.Join(step.builder.config.WorkingDir, constants.UploadScripts, script)
   375  		dst := filepath.Join(destinationDir, script)
   376  		glog.V(5).Infof("Copying file %q -> %q", src, dst)
   377  		if err := step.fs.MkdirAll(destinationDir); err != nil {
   378  			return fmt.Errorf("could not create directory %q: %v", destinationDir, err)
   379  		}
   380  		if err := step.fs.Copy(src, dst); err != nil {
   381  			return fmt.Errorf("could not copy file (%q -> %q): %v", src, dst, err)
   382  		}
   383  	}
   384  	return nil
   385  }
   386  
   387  type reportSuccessStep struct {
   388  	builder *STI
   389  }
   390  
   391  func (step *reportSuccessStep) execute(ctx *postExecutorStepContext) error {
   392  	glog.V(3).Info("Executing step: report success")
   393  
   394  	step.builder.result.Success = true
   395  
   396  	glog.V(3).Infof("Successfully built %s", utils.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 := utils.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[constants.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", constants.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  }