github.com/jdhenke/godel@v0.0.0-20161213181855-abeb3861bf0d/cmd/godel/update.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 godel
    16  
    17  import (
    18  	"io"
    19  	"io/ioutil"
    20  	"os"
    21  	"path"
    22  
    23  	"github.com/nmiyake/pkg/dirs"
    24  	"github.com/palantir/pkg/specdir"
    25  	"github.com/pkg/errors"
    26  
    27  	"github.com/palantir/godel/layout"
    28  )
    29  
    30  // NewInstall performs a new installation of gödel in the specified directory using the specified file as the source.
    31  // Calls "Install" to install the package provided as a parameter. Once the package is installed, the wrapper and
    32  // settings files are copied from the newly downloaded distribution to the specified path. If there was a previous
    33  // installation of gödel in the path, it is overwritten by the new file. However, changes in the "var" directory are
    34  // purely additive -- files that have been added in this directory in the new distribution will be added, but existing
    35  // files will not be modified or removed.
    36  func NewInstall(dstDirPath, srcPkgPath string, stdout io.Writer) error {
    37  	if err := layout.VerifyDirExists(dstDirPath); err != nil {
    38  		return errors.Wrapf(err, "Path %s does not specify an existing directory", dstDirPath)
    39  	}
    40  	if err := update(dstDirPath, PkgWithChecksum{
    41  		Pkg: srcPkgPath,
    42  	}, true, stdout); err != nil {
    43  		return errors.Wrapf(err, "Failed to install from %s into %s", srcPkgPath, dstDirPath)
    44  	}
    45  	return nil
    46  }
    47  
    48  // Update updates gödel. Calls "Install" to download and install the package specified in the "{{properties.Url}}"
    49  // property of the properties file for the provided gödel wrapper script. Once the package is installed, the provided
    50  // wrapper file and its directory are overwritten with the files provided by the package that was downloaded. However,
    51  // changes in the "var" directory are purely additive -- files that have been added in this directory in the new
    52  // distribution will be added, but existing files will not be modified or removed.
    53  func Update(wrapperScriptPath string, stdout io.Writer) error {
    54  	wrapperScriptDir := path.Dir(wrapperScriptPath)
    55  	wrapper, err := specdir.New(wrapperScriptDir, layout.WrapperSpec(), nil, specdir.Validate)
    56  	if err != nil {
    57  		return errors.Wrapf(err, "Wrapper script %s is not in a valid location", wrapperScriptPath)
    58  	}
    59  	pkg, err := GetDistPkgInfo(wrapper.Path(layout.WrapperConfigDir))
    60  	if err != nil {
    61  		return errors.Wrapf(err, "Failed to get URL from properties file")
    62  	}
    63  	if err := update(wrapperScriptDir, pkg, false, stdout); err != nil {
    64  		return errors.Wrapf(err, "Failed to update")
    65  	}
    66  	return nil
    67  }
    68  
    69  func update(wrapperScriptDir string, pkg PkgWithChecksum, newInstall bool, stdout io.Writer) error {
    70  	pkgSrc := pkg.ToPkgSrc()
    71  
    72  	mode := specdir.Validate
    73  	if newInstall {
    74  		mode = specdir.SpecOnly
    75  	}
    76  	wrapper, err := specdir.New(wrapperScriptDir, layout.WrapperSpec(), nil, mode)
    77  	if err != nil {
    78  		return errors.Wrapf(err, "%s is not a valid wrapper directory", wrapperScriptDir)
    79  	}
    80  
    81  	version, err := install(pkgSrc, stdout)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	gödelDist, err := layout.GödelDistLayout(version, specdir.Validate)
    87  	if err != nil {
    88  		return errors.Wrapf(err, "unable to get gödel home directory")
    89  	}
    90  
    91  	tmpDir, cleanup, err := dirs.TempDir(wrapperScriptDir, "")
    92  	defer cleanup()
    93  	if err != nil {
    94  		return errors.Wrapf(err, "failed to create temporary directory in %s", wrapperScriptDir)
    95  	}
    96  
    97  	// copy new wrapper script to temp directory on same device and then move to destination
    98  	installedGödelWrapper := gödelDist.Path(layout.WrapperScriptFile)
    99  	tmpGödelWrapper := path.Join(tmpDir, "godelw")
   100  	if err := layout.CopyFile(installedGödelWrapper, tmpGödelWrapper); err != nil {
   101  		return errors.Wrapf(err, "failed to copy %s to %s", installedGödelWrapper, tmpGödelWrapper)
   102  	}
   103  
   104  	if err := os.Rename(tmpGödelWrapper, wrapper.Path(layout.WrapperScriptFile)); err != nil {
   105  		return errors.Wrapf(err, "failed to move wrapper script into place")
   106  	}
   107  
   108  	if newInstall {
   109  		// if this is a new install, ensure that required destination paths exist
   110  		if err := layout.WrapperSpec().CreateDirectoryStructure(wrapperScriptDir, nil, false); err != nil {
   111  			return errors.Wrapf(err, "failed to ensure that required paths exist")
   112  		}
   113  	}
   114  
   115  	// additively sync config directory
   116  	if err := layout.SyncDirAdditive(gödelDist.Path(layout.WrapperConfigDir), wrapper.Path(layout.WrapperConfigDir)); err != nil {
   117  		return errors.Wrapf(err, "failed to additively sync from %s to %s", gödelDist.Path(layout.WrapperConfigDir), wrapper.Path(layout.WrapperConfigDir))
   118  	}
   119  
   120  	// overlay all directories except "config"
   121  	installedGödelWrapperDir := gödelDist.Path(layout.WrapperAppDir)
   122  	wrapperDirFiles, err := ioutil.ReadDir(installedGödelWrapperDir)
   123  	if err != nil {
   124  		return errors.Wrapf(err, "failed to list files in directory %s", installedGödelWrapperDir)
   125  	}
   126  
   127  	for _, currWrapperFile := range wrapperDirFiles {
   128  		syncSrcPath := path.Join(installedGödelWrapperDir, currWrapperFile.Name())
   129  		syncDestPath := path.Join(wrapperScriptDir, layout.AppName, currWrapperFile.Name())
   130  
   131  		if currWrapperFile.IsDir() && currWrapperFile.Name() == layout.WrapperConfigDir {
   132  			// do not sync "config" directory
   133  			continue
   134  		} else {
   135  			// if destination file exists, remove it
   136  			if _, err := os.Stat(syncDestPath); err == nil || !os.IsNotExist(err) {
   137  				if err := os.RemoveAll(syncDestPath); err != nil {
   138  					return errors.Wrapf(err, "failed to remove %s", syncDestPath)
   139  				}
   140  			}
   141  
   142  			// safe to copy
   143  			var err error
   144  			if currWrapperFile.IsDir() {
   145  				err = layout.CopyDir(syncSrcPath, syncDestPath)
   146  			} else {
   147  				err = layout.CopyFile(syncSrcPath, syncDestPath)
   148  			}
   149  			if err != nil {
   150  				return errors.Wrapf(err, "failed to copy %s to %s", syncSrcPath, syncDestPath)
   151  			}
   152  		}
   153  	}
   154  
   155  	return nil
   156  }