// packageNameDigest returns a filename to use for naming a package directory in // the file system. Using package names as is can introduce problems on file // systems with path length limits (on Windows in particular). Returns stripped // SHA1 of the whole package name on Windows. On Linux\Mac also prepends last // two components of the package name (for better readability of .cipd/* // directory). func packageNameDigest(pkg string) string { // Be paranoid. err := common.ValidatePackageName(pkg) if err != nil { panic(err.Error()) } // Grab stripped SHA1 of the full package name. digest := sha1.Sum([]byte(pkg)) hash := base64.URLEncoding.EncodeToString(digest[:])[:10] // On Windows paths are restricted to 260 chars, so every byte counts. if runtime.GOOS == "windows" { return hash } // On Posix file paths are not so restricted, so we can make names more // readable. Grab last <= 2 components of the package path and join them with // the digest. chunks := strings.Split(pkg, "/") if len(chunks) > 2 { chunks = chunks[len(chunks)-2:] } chunks = append(chunks, hash) return strings.Join(chunks, "_") }
func aclEndpoint(packagePath string) (string, error) { if err := common.ValidatePackageName(packagePath); err != nil { return "", err } params := url.Values{} params.Add("package_path", packagePath) return "repo/v1/acl?" + params.Encode(), nil }
func refEndpoint(packageName string, ref string) (string, error) { if err := common.ValidatePackageName(packageName); err != nil { return "", err } if err := common.ValidatePackageRef(ref); err != nil { return "", err } params := url.Values{} params.Add("package_name", packageName) params.Add("ref", ref) return "repo/v1/ref?" + params.Encode(), nil }
func (client *clientImpl) ResolveVersion(packageName, version string) (common.Pin, error) { if err := common.ValidatePackageName(packageName); err != nil { return common.Pin{}, err } // Is it instance ID already? Don't bother calling the backend. if common.ValidateInstanceID(version) == nil { return common.Pin{PackageName: packageName, InstanceID: version}, nil } if err := common.ValidateInstanceVersion(version); err != nil { return common.Pin{}, err } client.Logger.Debugf("cipd: resolving version %q of %q...", version, packageName) return client.remote.resolveVersion(packageName, version) }
func (client *clientImpl) ProcessEnsureFile(r io.Reader) ([]common.Pin, error) { lineNo := 0 makeError := func(msg string) error { return fmt.Errorf("Failed to parse desired state (line %d): %s", lineNo, msg) } out := []common.Pin{} scanner := bufio.NewScanner(r) for scanner.Scan() { lineNo++ // Split each line into words, ignore white space. tokens := []string{} for _, chunk := range strings.Split(scanner.Text(), " ") { chunk = strings.TrimSpace(chunk) if chunk != "" { tokens = append(tokens, chunk) } } // Skip empty lines or lines starting with '#'. if len(tokens) == 0 || tokens[0][0] == '#' { continue } // Each line has a format "<package name> <version>". if len(tokens) != 2 { return nil, makeError("expecting '<package name> <version>' line") } err := common.ValidatePackageName(tokens[0]) if err != nil { return nil, makeError(err.Error()) } err = common.ValidateInstanceVersion(tokens[1]) if err != nil { return nil, makeError(err.Error()) } // Good enough. pin, err := client.ResolveVersion(tokens[0], tokens[1]) if err != nil { return nil, err } out = append(out, pin) } return out, nil }
func (r *remoteImpl) resolveVersion(packageName, version string) (pin common.Pin, err error) { if err = common.ValidatePackageName(packageName); err != nil { return } if err = common.ValidateInstanceVersion(version); err != nil { return } var reply struct { Status string `json:"status"` ErrorMessage string `json:"error_message"` InstanceID string `json:"instance_id"` } params := url.Values{} params.Add("package_name", packageName) params.Add("version", version) err = r.makeRequest("repo/v1/instance/resolve?"+params.Encode(), "GET", nil, &reply) if err != nil { return } switch reply.Status { case "SUCCESS": if common.ValidateInstanceID(reply.InstanceID) != nil { err = fmt.Errorf("Backend returned invalid instance ID: %s", reply.InstanceID) } else { pin = common.Pin{PackageName: packageName, InstanceID: reply.InstanceID} } case "PACKAGE_NOT_FOUND": err = fmt.Errorf("Package '%s' is not registered", packageName) case "INSTANCE_NOT_FOUND": err = fmt.Errorf("Package '%s' doesn't have instance with version '%s'", packageName, version) case "AMBIGUOUS_VERSION": err = fmt.Errorf("More than one instance of package '%s' match version '%s'", packageName, version) case "ERROR": err = errors.New(reply.ErrorMessage) default: err = fmt.Errorf("Unexpected backend response: %s", reply.Status) } return }
func (d *deployerImpl) RemoveDeployed(packageName string) error { d.logger.Infof("Removing %s from %s", packageName, d.fs.Root()) if err := common.ValidatePackageName(packageName); err != nil { return err } pkgPath := d.packagePath(packageName) // Read the manifest of the currently installed version. manifest := Manifest{} currentID, err := d.getCurrentInstanceID(pkgPath) if err == nil && currentID != "" { manifest, err = d.readManifest(filepath.Join(pkgPath, currentID)) } // Warn, but continue with removal anyway. EnsureDirectoryGone call below // will nuke everything (even if it's half broken). if err != nil { d.logger.Warningf("Package %s is in a broken state: %s", packageName, err) } else { d.removeFromSiteRoot(manifest.Files) } return d.fs.EnsureDirectoryGone(pkgPath) }
// BuildInstance builds a new package instance for package named opts.PackageName // by archiving input files (passed via opts.Input). The final binary is written // to opts.Output. Some output may be written even if BuildInstance eventually // returns an error. func BuildInstance(opts BuildInstanceOptions) error { if opts.Logger == nil { opts.Logger = logging.Null() } err := common.ValidatePackageName(opts.PackageName) if err != nil { return err } // Make sure no files are written to package service directory. for _, f := range opts.Input { if strings.HasPrefix(f.Name(), packageServiceDir+"/") { return fmt.Errorf("Can't write to %s: %s", packageServiceDir, f.Name()) } } // Generate the manifest file, add to the list of input files. manifestFile, err := makeManifestFile(opts) if err != nil { return err } files := append(opts.Input, manifestFile) // Make sure filenames are unique. seenNames := make(map[string]struct{}, len(files)) for _, f := range files { _, seen := seenNames[f.Name()] if seen { return fmt.Errorf("File %s is provided twice", f.Name()) } seenNames[f.Name()] = struct{}{} } // Write the final zip file. return zipInputFiles(files, opts.Output, opts.Logger) }
// LoadPackageDef loads package definition from a YAML source code. In // substitutes %{...} strings in the definition with corresponding values // from 'vars' map. func LoadPackageDef(r io.Reader, vars map[string]string) (PackageDef, error) { data, err := ioutil.ReadAll(r) if err != nil { return PackageDef{}, err } out := PackageDef{} if err = yaml.Unmarshal(data, &out); err != nil { return PackageDef{}, err } // Substitute variables in all strings. for _, str := range out.strings() { *str, err = subVars(*str, vars) if err != nil { return PackageDef{}, err } } // Validate global package properties. if err = common.ValidatePackageName(out.Package); err != nil { return PackageDef{}, err } if err = ValidateInstallMode(out.InstallMode); err != nil { return PackageDef{}, err } versionFile := "" for i, chunk := range out.Data { // Make sure 'dir' and 'file' etc. aren't used together. has := []string{} if chunk.File != "" { has = append(has, "file") } if chunk.VersionFile != "" { has = append(has, "version_file") } if chunk.Dir != "" { has = append(has, "dir") } if len(has) == 0 { return out, fmt.Errorf("files entry #%d needs 'file', 'dir' or 'version_file' key", i) } if len(has) != 1 { return out, fmt.Errorf("files entry #%d should have only one key, got %q", i, has) } //'version_file' can appear only once, it must be a clean relative path. if chunk.VersionFile != "" { if versionFile != "" { return out, fmt.Errorf("'version_file' entry can be used only once") } versionFile = chunk.VersionFile if !isCleanSlashPath(versionFile) { return out, fmt.Errorf("'version_file' must be a path relative to the package root: %s", versionFile) } } } // Default 'root' to a directory with the package def file. if out.Root == "" { out.Root = "." } return out, nil }