github.com/openshift/source-to-image@v1.4.1-0.20240516041539-bf52fc02204e/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  	"github.com/openshift/source-to-image/pkg/api/constants"
    17  	dockerpkg "github.com/openshift/source-to-image/pkg/docker"
    18  	s2ierr "github.com/openshift/source-to-image/pkg/errors"
    19  	s2itar "github.com/openshift/source-to-image/pkg/tar"
    20  	"github.com/openshift/source-to-image/pkg/util"
    21  	"github.com/openshift/source-to-image/pkg/util/fs"
    22  	utilstatus "github.com/openshift/source-to-image/pkg/util/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  		log.V(3).Info("Executing step: store previous image")
    64  		ctx.previousImageID = step.getPreviousImage()
    65  		return nil
    66  	}
    67  
    68  	log.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  		log.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  		log.V(3).Info("Executing step: remove previous image")
    89  		step.removePreviousImage(ctx.previousImageID)
    90  		return nil
    91  	}
    92  
    93  	log.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  	log.V(1).Infof("Removing previously-tagged image %s", previousImageID)
   103  	if err := step.docker.RemoveImage(previousImageID); err != nil {
   104  		log.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  	log.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  	log.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  			log.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  			log.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  	log.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  		User:            step.builder.config.AssembleRuntimeUser,
   332  	}
   333  
   334  	opts.OnStart = func(containerID string) error {
   335  		setStandardPerms := func(writer io.Writer) s2itar.Writer {
   336  			return s2itar.ChmodAdapter{Writer: tar.NewWriter(writer), NewFileMode: 0644, NewExecFileMode: 0755, NewDirMode: 0755}
   337  		}
   338  
   339  		log.V(5).Infof("Uploading directory %q -> %q", artifactsDir, workDir)
   340  		onStartErr := step.docker.UploadToContainerWithTarWriter(step.fs, artifactsDir, workDir, containerID, setStandardPerms)
   341  		if onStartErr != nil {
   342  			return fmt.Errorf("could not upload directory (%q -> %q) into container %s: %v", artifactsDir, workDir, containerID, err)
   343  		}
   344  
   345  		log.V(5).Infof("Uploading file %q -> %q", lastFilePath, lastFileDstPath)
   346  		onStartErr = step.docker.UploadToContainerWithTarWriter(step.fs, lastFilePath, lastFileDstPath, containerID, setStandardPerms)
   347  		if onStartErr != nil {
   348  			return fmt.Errorf("could not upload file (%q -> %q) into container %s: %v", lastFilePath, lastFileDstPath, containerID, err)
   349  		}
   350  
   351  		return onStartErr
   352  	}
   353  
   354  	dockerpkg.StreamContainerIO(outReader, nil, func(s string) { log.V(0).Info(s) })
   355  
   356  	errOutput := ""
   357  	c := dockerpkg.StreamContainerIO(errReader, &errOutput, func(s string) { log.Info(s) })
   358  
   359  	// switch to the next stage of post executors steps
   360  	step.builder.postExecutorStage++
   361  
   362  	err = step.docker.RunContainer(opts)
   363  	if e, ok := err.(s2ierr.ContainerError); ok {
   364  		// Must wait for StreamContainerIO goroutine above to exit before reading errOutput.
   365  		<-c
   366  		err = s2ierr.NewContainerError(image, e.ErrorCode, errOutput+e.Output)
   367  	}
   368  
   369  	return err
   370  }
   371  
   372  func (step *startRuntimeImageAndUploadFilesStep) copyScriptIfNeeded(script, destinationDir string) error {
   373  	useExternalScript := step.builder.externalScripts[script]
   374  	if useExternalScript {
   375  		src := filepath.Join(step.builder.config.WorkingDir, constants.UploadScripts, script)
   376  		dst := filepath.Join(destinationDir, script)
   377  		log.V(5).Infof("Copying file %q -> %q", src, dst)
   378  		if err := step.fs.MkdirAll(destinationDir); err != nil {
   379  			return fmt.Errorf("could not create directory %q: %v", destinationDir, err)
   380  		}
   381  		if err := step.fs.Copy(src, dst, nil); err != nil {
   382  			return fmt.Errorf("could not copy file (%q -> %q): %v", src, dst, err)
   383  		}
   384  	}
   385  	return nil
   386  }
   387  
   388  type reportSuccessStep struct {
   389  	builder *STI
   390  }
   391  
   392  func (step *reportSuccessStep) execute(ctx *postExecutorStepContext) error {
   393  	log.V(3).Info("Executing step: report success")
   394  
   395  	step.builder.result.Success = true
   396  	step.builder.result.ImageID = ctx.imageID
   397  
   398  	log.V(3).Infof("Successfully built %s", util.FirstNonEmpty(step.builder.config.Tag, ctx.imageID))
   399  
   400  	return nil
   401  }
   402  
   403  // shared methods
   404  
   405  func commitContainer(docker dockerpkg.Docker, containerID, cmd, user, tag string, env, entrypoint []string, labels map[string]string) (string, error) {
   406  	opts := dockerpkg.CommitContainerOptions{
   407  		Command:     []string{cmd},
   408  		Env:         env,
   409  		Entrypoint:  entrypoint,
   410  		ContainerID: containerID,
   411  		Repository:  tag,
   412  		User:        user,
   413  		Labels:      labels,
   414  	}
   415  
   416  	imageID, err := docker.CommitContainer(opts)
   417  	if err != nil {
   418  		return "", s2ierr.NewCommitError(tag, err)
   419  	}
   420  
   421  	return imageID, nil
   422  }
   423  
   424  func createLabelsForResultingImage(builder *STI, docker dockerpkg.Docker, baseImage string) map[string]string {
   425  	generatedLabels := util.GenerateOutputImageLabels(builder.sourceInfo, builder.config)
   426  
   427  	existingLabels, err := docker.GetLabels(baseImage)
   428  	if err != nil {
   429  		log.V(0).Infof("error: Unable to read existing labels from the base image %s", baseImage)
   430  	}
   431  
   432  	configLabels := builder.config.Labels
   433  	newLabels := builder.newLabels
   434  
   435  	return mergeLabels(existingLabels, generatedLabels, configLabels, newLabels)
   436  }
   437  
   438  func mergeLabels(labels ...map[string]string) map[string]string {
   439  	mergedLabels := map[string]string{}
   440  
   441  	for _, labelMap := range labels {
   442  		for k, v := range labelMap {
   443  			mergedLabels[k] = v
   444  		}
   445  	}
   446  	return mergedLabels
   447  }
   448  
   449  func createCommandForExecutingRunScript(scriptsURL map[string]string, location string) string {
   450  	cmd := scriptsURL[constants.Run]
   451  	if strings.HasPrefix(cmd, "image://") {
   452  		// scripts from inside of the image, we need to strip the image part
   453  		// NOTE: We use path.Join instead of filepath.Join to avoid converting the
   454  		// path to UNC (Windows) format as we always run this inside container.
   455  		cmd = strings.TrimPrefix(cmd, "image://")
   456  	} else {
   457  		// external scripts, in which case we're taking the directory to which they
   458  		// were extracted and append scripts dir and name
   459  		cmd = path.Join(location, "scripts", constants.Run)
   460  	}
   461  	return cmd
   462  }
   463  
   464  func downloadAndExtractFileFromContainer(docker dockerpkg.Docker, tar s2itar.Tar, sourcePath, destinationPath, containerID string) (api.FailureReason, error) {
   465  	log.V(5).Infof("Downloading file %q", sourcePath)
   466  
   467  	fd, err := ioutil.TempFile(destinationPath, "s2i-runtime-artifact")
   468  	if err != nil {
   469  		res := utilstatus.NewFailureReason(
   470  			utilstatus.ReasonFSOperationFailed,
   471  			utilstatus.ReasonMessageFSOperationFailed,
   472  		)
   473  		return res, fmt.Errorf("could not create temporary file for runtime artifact: %v", err)
   474  	}
   475  	defer func() {
   476  		fd.Close()
   477  		os.Remove(fd.Name())
   478  	}()
   479  
   480  	if err := docker.DownloadFromContainer(sourcePath, fd, containerID); err != nil {
   481  		res := utilstatus.NewFailureReason(
   482  			utilstatus.ReasonGenericS2IBuildFailed,
   483  			utilstatus.ReasonMessageGenericS2iBuildFailed,
   484  		)
   485  		return res, fmt.Errorf("could not download file (%q -> %q) from container %s: %v", sourcePath, fd.Name(), containerID, err)
   486  	}
   487  
   488  	// after writing to the file descriptor we need to rewind pointer to the beginning of the file before next reading
   489  	if _, err := fd.Seek(0, io.SeekStart); err != nil {
   490  		res := utilstatus.NewFailureReason(
   491  			utilstatus.ReasonGenericS2IBuildFailed,
   492  			utilstatus.ReasonMessageGenericS2iBuildFailed,
   493  		)
   494  		return res, fmt.Errorf("could not seek to the beginning of the file %q: %v", fd.Name(), err)
   495  	}
   496  
   497  	if err := tar.ExtractTarStream(destinationPath, fd); err != nil {
   498  		res := utilstatus.NewFailureReason(
   499  			utilstatus.ReasonGenericS2IBuildFailed,
   500  			utilstatus.ReasonMessageGenericS2iBuildFailed,
   501  		)
   502  		return res, fmt.Errorf("could not extract artifact %q into the directory %q: %v", sourcePath, destinationPath, err)
   503  	}
   504  
   505  	return utilstatus.NewFailureReason("", ""), nil
   506  }
   507  
   508  func checkLabelSize(labels map[string]string) error {
   509  	var sum = 0
   510  	for k, v := range labels {
   511  		sum += len(k) + len(v)
   512  	}
   513  
   514  	if sum > maximumLabelSize {
   515  		return fmt.Errorf("label size '%d' exceeds the maximum limit '%d'", sum, maximumLabelSize)
   516  	}
   517  
   518  	return nil
   519  }
   520  
   521  // check for new labels and apply to the output image.
   522  func checkAndGetNewLabels(builder *STI, docker dockerpkg.Docker, tar s2itar.Tar, containerID string) error {
   523  	log.V(3).Infof("Checking for new Labels to apply... ")
   524  
   525  	// metadata filename and its path inside the container
   526  	metadataFilename := "image_metadata.json"
   527  	sourceFilepath := filepath.Join("/tmp/.s2i", metadataFilename)
   528  
   529  	// create the 'downloadPath' folder if it doesn't exist
   530  	downloadPath := filepath.Join(builder.config.WorkingDir, "metadata")
   531  	log.V(3).Infof("Creating the download path '%s'", downloadPath)
   532  	if err := os.MkdirAll(downloadPath, 0700); err != nil {
   533  		log.Errorf("Error creating dir %q for '%s': %v", downloadPath, metadataFilename, err)
   534  		return err
   535  	}
   536  
   537  	// download & extract the file from container
   538  	if _, err := downloadAndExtractFileFromContainer(docker, tar, sourceFilepath, downloadPath, containerID); err != nil {
   539  		log.V(3).Infof("unable to download and extract '%s' ... continuing", metadataFilename)
   540  		return nil
   541  	}
   542  
   543  	// open the file
   544  	filePath := filepath.Join(downloadPath, metadataFilename)
   545  	fd, err := os.Open(filePath)
   546  	if fd == nil || err != nil {
   547  		return fmt.Errorf("unable to open file '%s' : %v", downloadPath, err)
   548  	}
   549  	defer fd.Close()
   550  
   551  	// read the file to a string
   552  	str, err := ioutil.ReadAll(fd)
   553  	if err != nil {
   554  		return fmt.Errorf("error reading file '%s' in to a string: %v", filePath, err)
   555  	}
   556  	log.V(3).Infof("new Labels File contents : \n%s\n", str)
   557  
   558  	// string into a map
   559  	var data map[string]interface{}
   560  
   561  	if err = json.Unmarshal([]byte(str), &data); err != nil {
   562  		return fmt.Errorf("JSON Unmarshal Error with '%s' file : %v", metadataFilename, err)
   563  	}
   564  
   565  	// update newLabels[]
   566  	labels := data["labels"]
   567  	for _, l := range labels.([]interface{}) {
   568  		for k, v := range l.(map[string]interface{}) {
   569  			builder.newLabels[k] = v.(string)
   570  		}
   571  	}
   572  
   573  	return nil
   574  }