| Index: go/src/infra/tools/cipd/local/deployer.go
|
| diff --git a/go/src/infra/tools/cipd/deployer.go b/go/src/infra/tools/cipd/local/deployer.go
|
| similarity index 76%
|
| rename from go/src/infra/tools/cipd/deployer.go
|
| rename to go/src/infra/tools/cipd/local/deployer.go
|
| index 68bace5278635e72b9970c813a849ddf1737f2ad..9c490b93e97bb5bf37eb0157c482276c1572445e 100644
|
| --- a/go/src/infra/tools/cipd/deployer.go
|
| +++ b/go/src/infra/tools/cipd/local/deployer.go
|
| @@ -2,7 +2,7 @@
|
| // Use of this source code is governed by a BSD-style license that can be
|
| // found in the LICENSE file.
|
|
|
| -package cipd
|
| +package local
|
|
|
| import (
|
| "crypto/sha1"
|
| @@ -15,9 +15,12 @@ import (
|
| "strings"
|
| "sync"
|
| "time"
|
| +
|
| + "infra/libs/logging"
|
| + "infra/tools/cipd/common"
|
| )
|
|
|
| -// TODO(vadimsh): Make it work on Windows, verify it works on Mac.
|
| +// TODO(vadimsh): Make it work on Windows.
|
|
|
| // TODO(vadimsh): How to handle path conflicts between two packages? Currently
|
| // the last one installed wins.
|
| @@ -42,74 +45,67 @@ import (
|
| // Some efforts are made to make sure that during the deployment a window of
|
| // inconsistency in the file system is as small as possible.
|
|
|
| -// Subdirectory of site root to extract packages to.
|
| -const packagesDir = siteServiceDir + "/pkgs"
|
| +// packagesDir is a subdirectory of site root to extract packages to.
|
| +const packagesDir = SiteServiceDir + "/pkgs"
|
|
|
| -// Name of a symlink that points to latest deployed version.
|
| +// currentSymlink is a name of a symlink that points to latest deployed version.
|
| const currentSymlink = "_current"
|
|
|
| -// PackageState contains information about single deployed package.
|
| -type PackageState struct {
|
| - // PackageName identifies the package.
|
| - PackageName string
|
| - // InstanceID is ID of the installed package instance (SHA1 of package contents).
|
| - InstanceID string
|
| -}
|
| +// log is logger to use for logs produced by this file.
|
| +// TODO(vadimsh): Propagate as parameter.
|
| +var log = logging.DefaultLogger
|
|
|
| -// DeployInstance installs a specific instance of a package (identified by
|
| -// InstanceID()) into a site root directory. It unpacks the package into
|
| -// <root>/.cipd/pkgs/*, and rearranges symlinks to point to unpacked files.
|
| -// It tries to make it as "atomic" as possibly.
|
| -func DeployInstance(root string, inst PackageInstance) (state PackageState, err error) {
|
| - root, err = filepath.Abs(filepath.Clean(root))
|
| +// DeployInstance installs a specific instance of a package into a site root
|
| +// directory. It unpacks the package into <root>/.cipd/pkgs/*, and rearranges
|
| +// symlinks to point to unpacked files. It tries to make it as "atomic" as
|
| +// possible. Returns information about the deployed instance.
|
| +func DeployInstance(root string, inst PackageInstance) (common.Pin, error) {
|
| + root, err := filepath.Abs(filepath.Clean(root))
|
| if err != nil {
|
| - return
|
| + return common.Pin{}, err
|
| }
|
| - log.Infof("Deploying %s:%s into %s", inst.PackageName(), inst.InstanceID(), root)
|
| + pin := inst.Pin()
|
| + log.Infof("Deploying %s into %s", pin, root)
|
|
|
| // Be paranoid.
|
| - err = ValidatePackageName(inst.PackageName())
|
| + err = common.ValidatePin(pin)
|
| if err != nil {
|
| - return
|
| - }
|
| - err = ValidateInstanceID(inst.InstanceID())
|
| - if err != nil {
|
| - return
|
| + return common.Pin{}, err
|
| }
|
|
|
| // Remember currently deployed version (to remove it later). Do not freak out
|
| // if it's not there (prevID is "" in that case).
|
| oldFiles := makeStringSet()
|
| - prevID := findDeployedInstance(root, inst.PackageName(), oldFiles)
|
| + prevID := findDeployedInstance(root, pin.PackageName, oldFiles)
|
|
|
| // Extract new version to a final destination.
|
| newFiles := makeStringSet()
|
| destPath, err := deployInstance(root, inst, newFiles)
|
| if err != nil {
|
| - return
|
| + return common.Pin{}, err
|
| }
|
|
|
| // Switch '_current' symlink to point to a new package instance. It is a
|
| // point of no return. The function must not fail going forward.
|
| - mainSymlinkPath := packagePath(root, inst.PackageName(), currentSymlink)
|
| - err = ensureSymlink(mainSymlinkPath, inst.InstanceID())
|
| + mainSymlinkPath := packagePath(root, pin.PackageName, currentSymlink)
|
| + err = ensureSymlink(mainSymlinkPath, pin.InstanceID)
|
| if err != nil {
|
| ensureDirectoryGone(destPath)
|
| - return
|
| + return common.Pin{}, err
|
| }
|
|
|
| // Asynchronously remove previous version (best effort).
|
| wg := sync.WaitGroup{}
|
| defer wg.Wait()
|
| - if prevID != "" && prevID != inst.InstanceID() {
|
| + if prevID != "" && prevID != pin.InstanceID {
|
| wg.Add(1)
|
| go func() {
|
| defer wg.Done()
|
| - ensureDirectoryGone(packagePath(root, inst.PackageName(), prevID))
|
| + ensureDirectoryGone(packagePath(root, pin.PackageName, prevID))
|
| }()
|
| }
|
|
|
| - log.Infof("Adjusting symlinks for %s", inst.PackageName())
|
| + log.Infof("Adjusting symlinks for %s", pin.PackageName)
|
|
|
| // Make symlinks in the site directory for all new files. Reference a package
|
| // root via '_current' symlink (instead of direct destPath), to make
|
| @@ -123,33 +119,30 @@ func DeployInstance(root string, inst PackageInstance) (state PackageState, err
|
| }
|
|
|
| // Verify it's all right, read the manifest.
|
| - state, err = CheckDeployed(root, inst.PackageName())
|
| - if err == nil && state.InstanceID != inst.InstanceID() {
|
| - err = fmt.Errorf("Other package instance (%s) was deployed concurrently", state.InstanceID)
|
| + newPin, err := CheckDeployed(root, pin.PackageName)
|
| + if err == nil && newPin.InstanceID != pin.InstanceID {
|
| + err = fmt.Errorf("Other instance (%s) was deployed concurrently", newPin.InstanceID)
|
| }
|
| if err == nil {
|
| - log.Infof("Successfully deployed %s:%s", inst.PackageName(), inst.InstanceID())
|
| + log.Infof("Successfully deployed %s", pin)
|
| } else {
|
| - log.Errorf("Failed to deploy %s:%s: %s", inst.PackageName(), inst.InstanceID(), err.Error())
|
| + log.Errorf("Failed to deploy %s: %s", pin, err)
|
| }
|
| - return
|
| + return newPin, err
|
| }
|
|
|
| // CheckDeployed checks whether a given package is deployed and returns
|
| // information about it if it is.
|
| -func CheckDeployed(root string, pkg string) (state PackageState, err error) {
|
| - state, err = readPackageState(packagePath(root, pkg))
|
| - if err != nil {
|
| - return
|
| - }
|
| - if state.PackageName != pkg {
|
| +func CheckDeployed(root string, pkg string) (common.Pin, error) {
|
| + pin, err := readPackageState(packagePath(root, pkg))
|
| + if err == nil && pin.PackageName != pkg {
|
| err = fmt.Errorf("Package path and package name in the manifest do not match")
|
| }
|
| - return
|
| + return pin, err
|
| }
|
|
|
| // FindDeployed returns a list of packages deployed to a site root.
|
| -func FindDeployed(root string) (out []PackageState, err error) {
|
| +func FindDeployed(root string) (out []common.Pin, err error) {
|
| root, err = filepath.Abs(filepath.Clean(root))
|
| if err != nil {
|
| return
|
| @@ -167,18 +160,18 @@ func FindDeployed(root string) (out []PackageState, err error) {
|
| }
|
|
|
| // Read the package name from the package manifest. Skip broken stuff.
|
| - found := map[string]PackageState{}
|
| + found := map[string]common.Pin{}
|
| keys := []string{}
|
| for _, info := range infos {
|
| // Attempt to read the manifest. If it is there -> valid package is found.
|
| if info.IsDir() {
|
| - state, err := readPackageState(filepath.Join(pkgs, info.Name()))
|
| + pin, err := readPackageState(filepath.Join(pkgs, info.Name()))
|
| if err == nil {
|
| // Ignore duplicate entries, they can appear if someone messes with
|
| // pkgs/* structure manually.
|
| - if _, ok := found[state.PackageName]; !ok {
|
| - keys = append(keys, state.PackageName)
|
| - found[state.PackageName] = state
|
| + if _, ok := found[pin.PackageName]; !ok {
|
| + keys = append(keys, pin.PackageName)
|
| + found[pin.PackageName] = pin
|
| }
|
| }
|
| }
|
| @@ -186,7 +179,7 @@ func FindDeployed(root string) (out []PackageState, err error) {
|
|
|
| // Sort by package name.
|
| sort.Strings(keys)
|
| - out = make([]PackageState, len(found))
|
| + out = make([]common.Pin, len(found))
|
| for i, k := range keys {
|
| out[i] = found[k]
|
| }
|
| @@ -202,7 +195,7 @@ func RemoveDeployed(root string, packageName string) error {
|
| log.Infof("Removing %s from %s", packageName, root)
|
|
|
| // Be paranoid.
|
| - err = ValidatePackageName(packageName)
|
| + err = common.ValidatePackageName(packageName)
|
| if err != nil {
|
| return err
|
| }
|
| @@ -244,13 +237,13 @@ func deployInstance(root string, inst PackageInstance, files stringSet) (string,
|
| // Extract new version to a final destination. ExtractPackageInstance knows
|
| // how to build full paths and how to atomically extract a package. No need
|
| // to delete garbage if it fails.
|
| - destPath := packagePath(root, inst.PackageName(), inst.InstanceID())
|
| + destPath := packagePath(root, inst.Pin().PackageName, inst.Pin().InstanceID)
|
| err := ExtractInstance(inst, NewFileSystemDestination(destPath))
|
| if err != nil {
|
| return "", err
|
| }
|
| // Enumerate files inside. Nuke it and fail if it's unreadable.
|
| - err = scanPackageDir(packagePath(root, inst.PackageName(), inst.InstanceID()), files)
|
| + err = scanPackageDir(packagePath(root, inst.Pin().PackageName, inst.Pin().InstanceID), files)
|
| if err != nil {
|
| ensureDirectoryGone(destPath)
|
| return "", err
|
| @@ -270,12 +263,12 @@ func linkFilesToRoot(root string, packageRoot string, files stringSet) {
|
| // E.g. ../.cipd/pkgs/name/_current/bin/tool.
|
| targetRel, err := filepath.Rel(filepath.Dir(symlinkAbs), targetAbs)
|
| if err != nil {
|
| - log.Warnf("Can't get relative path from %s to %s", filepath.Dir(symlinkAbs), targetAbs)
|
| + log.Warningf("Can't get relative path from %s to %s", filepath.Dir(symlinkAbs), targetAbs)
|
| continue
|
| }
|
| err = ensureSymlink(symlinkAbs, targetRel)
|
| if err != nil {
|
| - log.Warnf("Failed to create symlink for %s", relPath)
|
| + log.Warningf("Failed to create symlink for %s", relPath)
|
| continue
|
| }
|
| }
|
| @@ -307,7 +300,7 @@ func packagePath(root string, pkg string, rest ...string) string {
|
| // components of the package name + stripped SHA1 of the whole package name.
|
| func packageNameDigest(pkg string) string {
|
| // Be paranoid.
|
| - err := ValidatePackageName(pkg)
|
| + err := common.ValidatePackageName(pkg)
|
| if err != nil {
|
| panic(err.Error())
|
| }
|
| @@ -329,34 +322,32 @@ func packageNameDigest(pkg string) string {
|
| }
|
|
|
| // readPackageState reads package manifest of a deployed package instance and
|
| -// returns corresponding PackageState object.
|
| -func readPackageState(packageDir string) (state PackageState, err error) {
|
| +// returns corresponding Pin object.
|
| +func readPackageState(packageDir string) (common.Pin, error) {
|
| // Resolve _current symlink to a concrete instance ID.
|
| current, err := os.Readlink(filepath.Join(packageDir, currentSymlink))
|
| if err != nil {
|
| - return
|
| + return common.Pin{}, err
|
| }
|
| - err = ValidateInstanceID(current)
|
| + err = common.ValidateInstanceID(current)
|
| if err != nil {
|
| - err = fmt.Errorf("Symlink target doesn't look like a valid instance id")
|
| - return
|
| + return common.Pin{}, fmt.Errorf("Symlink target doesn't look like a valid instance id")
|
| }
|
| // Read the manifest from the instance directory.
|
| manifestPath := filepath.Join(packageDir, current, filepath.FromSlash(manifestName))
|
| r, err := os.Open(manifestPath)
|
| if err != nil {
|
| - return
|
| + return common.Pin{}, err
|
| }
|
| defer r.Close()
|
| manifest, err := readManifest(r)
|
| if err != nil {
|
| - return
|
| + return common.Pin{}, err
|
| }
|
| - state = PackageState{
|
| + return common.Pin{
|
| PackageName: manifest.PackageName,
|
| InstanceID: current,
|
| - }
|
| - return
|
| + }, nil
|
| }
|
|
|
| // ensureSymlink atomically creates a symlink pointing to a target. It will
|
| @@ -405,7 +396,7 @@ func scanPackageDir(root string, out stringSet) error {
|
| if err != nil {
|
| return err
|
| }
|
| - if rel == packageServiceDir || rel == siteServiceDir {
|
| + if rel == packageServiceDir || rel == SiteServiceDir {
|
| return filepath.SkipDir
|
| }
|
| if info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 {
|
| @@ -422,14 +413,14 @@ func ensureDirectoryGone(path string) error {
|
| err := os.Rename(path, temp)
|
| if err != nil {
|
| if !os.IsNotExist(err) {
|
| - log.Warnf("Failed to rename directory %s: %v", path, err)
|
| + log.Warningf("Failed to rename directory %s: %v", path, err)
|
| return err
|
| }
|
| return nil
|
| }
|
| err = os.RemoveAll(temp)
|
| if err != nil {
|
| - log.Warnf("Failed to remove directory %s: %v", temp, err)
|
| + log.Warningf("Failed to remove directory %s: %v", temp, err)
|
| return err
|
| }
|
| return nil
|
| @@ -439,7 +430,7 @@ func ensureDirectoryGone(path string) error {
|
| func ensureFileGone(path string) error {
|
| err := os.Remove(path)
|
| if err != nil && !os.IsNotExist(err) {
|
| - log.Warnf("Failed to remove %s", path)
|
| + log.Warningf("Failed to remove %s", path)
|
| return err
|
| }
|
| return nil
|
|
|