// Requirements returns all relevant options from the [X-Fleet] section of a unit file. // Relevant options are identified with a `X-` prefix in the unit. // This prefix is stripped from relevant options before being returned. // Furthermore, specifier substitution (using unitPrintf) is performed on all requirements. func (j *Job) Requirements() map[string][]string { uni := unit.NewUnitNameInfo(j.Name) requirements := make(map[string][]string) for key, values := range j.Unit.Contents["X-Fleet"] { if !strings.HasPrefix(key, "X-") { continue } // Strip off leading X- key = key[2:] if _, ok := requirements[key]; !ok { requirements[key] = make([]string, 0) } if uni != nil { for i, v := range values { values[i] = unitPrintf(v, *uni) } } requirements[key] = values } return requirements }
// isLocalUnitDifferent compares a Unit on the file system with a one // provided from the Registry. // isLocalUnitDifferent first tries to load the passed Unit from the // local file system and compares it with the Unit that is in the // Registry. If it fails to load that Unit from the filesystem and // fatal was not set, it will check again if that file name is an // instance of a template, if so it will load the template Unit and // compare it with the provided Unit. // It takes three arguments; a path to the local Unit on the file system, // the Unit in the registry, and a last boolean to fail in case fatal errors // happen. // Returns true if the local Unit on file system is different from the // one provided, false otherwise; and any error encountered. func isLocalUnitDifferent(cCmd *cobra.Command, file string, su *schema.Unit, fatal bool) (bool, error) { replace, _ := cCmd.Flags().GetBool("replace") result, err := matchLocalFileAndUnit(file, su) if err == nil { // Warn in case unit differs from local file if result == false && !replace { stderr("WARNING: Unit %s in registry differs from local unit file %s. Add --replace to override.", su.Name, file) } return !result, nil } else if fatal { return false, err } info := unit.NewUnitNameInfo(path.Base(file)) if info == nil { return false, fmt.Errorf("error extracting information from unit name %s", file) } else if !info.IsInstance() { return false, fmt.Errorf("error Unit %s does not seem to be a template unit", file) } templFile := path.Join(path.Dir(file), info.Template) result, err = matchLocalFileAndUnit(templFile, su) if err == nil { // Warn in case unit differs from local template unit file if result == false && !replace { stderr("WARNING: Unit %s in registry differs from local template unit file %s. Add --replace to override.", su.Name, file) } return !result, nil } return false, err }
func (ur *unitsResource) set(rw http.ResponseWriter, req *http.Request, item string) { if err := validateContentType(req); err != nil { sendError(rw, http.StatusUnsupportedMediaType, err) return } var su schema.Unit dec := json.NewDecoder(req.Body) err := dec.Decode(&su) if err != nil { sendError(rw, http.StatusBadRequest, fmt.Errorf("unable to decode body: %v", err)) return } if su.Name == "" { su.Name = item } if item != su.Name { sendError(rw, http.StatusBadRequest, fmt.Errorf("name in URL %q differs from unit name in request body %q", item, su.Name)) return } if err := ValidateName(su.Name); err != nil { sendError(rw, http.StatusBadRequest, err) return } eu, err := ur.cAPI.Unit(su.Name) if err != nil { log.Errorf("Failed fetching Unit(%s) from Registry: %v", su.Name, err) sendError(rw, http.StatusInternalServerError, nil) return } if eu == nil { if len(su.Options) == 0 { err := errors.New("unit does not exist and options field empty") sendError(rw, http.StatusConflict, err) } else if err := ValidateOptions(su.Options); err != nil { sendError(rw, http.StatusBadRequest, err) } else { ur.create(rw, su.Name, &su) } return } if len(su.DesiredState) == 0 { err := errors.New("must provide DesiredState to update existing unit") sendError(rw, http.StatusConflict, err) return } un := unit.NewUnitNameInfo(su.Name) if un.IsTemplate() && job.JobState(su.DesiredState) != job.JobStateInactive { err := fmt.Errorf("cannot activate template %q", su.Name) sendError(rw, http.StatusBadRequest, err) return } ur.update(rw, su.Name, su.DesiredState) }
// requirements returns all relevant options from the [X-Fleet] section of a unit file. // Relevant options are identified with a `X-` prefix in the unit. // This prefix is stripped from relevant options before being returned. // Furthermore, specifier substitution (using unitPrintf) is performed on all requirements. func (j *Job) requirements() map[string][]string { uni := unit.NewUnitNameInfo(j.Name) requirements := make(map[string][]string) for key, values := range j.Unit.Contents["X-Fleet"] { if _, ok := requirements[key]; !ok { requirements[key] = make([]string, 0) } if uni != nil { for i, v := range values { values[i] = unitPrintf(v, *uni) } } requirements[key] = values } return requirements }
func warnOnDifferentLocalUnit(name string, j *job.Job) { if _, err := os.Stat(name); !os.IsNotExist(err) { unit, err := getUnitFromFile(name) if err == nil && unit.Hash() != j.Unit.Hash() { fmt.Fprintf(os.Stderr, "WARNING: Job(%s) in Registry differs from local Unit(%s)\n", j.Name, name) return } } if uni := unit.NewUnitNameInfo(path.Base(name)); uni != nil && uni.IsInstance() { file := path.Join(path.Dir(name), uni.Template) if _, err := os.Stat(file); !os.IsNotExist(err) { tmpl, err := getUnitFromFile(file) if err == nil && tmpl.Hash() != j.Unit.Hash() { fmt.Fprintf(os.Stderr, "WARNING: Job(%s) in Registry differs from local template Unit(%s)\n", j.Name, uni.Template) } } } }
func warnOnDifferentLocalUnit(loc string, su *schema.Unit) { suf := schema.MapSchemaUnitOptionsToUnitFile(su.Options) if _, err := os.Stat(loc); !os.IsNotExist(err) { luf, err := getUnitFromFile(loc) if err == nil && luf.Hash() != suf.Hash() { stderr("WARNING: Unit %s in registry differs from local unit file %s", su.Name, loc) return } } if uni := unit.NewUnitNameInfo(path.Base(loc)); uni != nil && uni.IsInstance() { file := path.Join(path.Dir(loc), uni.Template) if _, err := os.Stat(file); !os.IsNotExist(err) { tmpl, err := getUnitFromFile(file) if err == nil && tmpl.Hash() != suf.Hash() { stderr("WARNING: Unit %s in registry differs from local template unit file %s", su.Name, uni.Template) } } } }
func TestInstanceUnitPrintf(t *testing.T) { u := unit.NewUnitNameInfo("*****@*****.**") if u == nil { t.Fatal("NewNamedUnit returned nil - aborting") } for _, tt := range []struct { in string want string }{ {"%n", "*****@*****.**"}, {"%N", "foo@bar"}, {"%p", "foo"}, {"%i", "bar"}, } { got := unitPrintf(tt.in, *u) if got != tt.want { t.Errorf("Replacement of %q failed: got %q, want %q", tt.in, got, tt.want) } } }
// getUnitFile attempts to get a UnitFile configuration // It takes a unit file name as a parameter and tries first to lookup // the unit from the local disk. If it fails, it checks if the provided // file name may reference an instance of a template unit, if so, it // tries to get the template configuration either from the registry or // the local disk. // It returns a UnitFile configuration or nil; and any error ecountered func getUnitFile(cCmd *cobra.Command, file string) (*unit.UnitFile, error) { var uf *unit.UnitFile name := unitNameMangle(file) log.Debugf("Looking for Unit(%s) or its corresponding template", name) // Assume that the file references a local unit file on disk and // attempt to load it, if it exists if _, err := os.Stat(file); !os.IsNotExist(err) { uf, err = getUnitFromFile(file) if err != nil { return nil, fmt.Errorf("failed getting Unit(%s) from file: %v", file, err) } } else { // Otherwise (if the unit file does not exist), check if the // name appears to be an instance of a template unit info := unit.NewUnitNameInfo(name) if info == nil { return nil, fmt.Errorf("error extracting information from unit name %s", name) } else if !info.IsInstance() { return nil, fmt.Errorf("unable to find Unit(%s) in Registry or on filesystem", name) } // If it is an instance check for a corresponding template // unit in the Registry or disk. // If we found a template unit, later we create a // near-identical instance unit in the Registry - same // unit file as the template, but different name uf, err = getUnitFileFromTemplate(cCmd, info, file) if err != nil { return nil, fmt.Errorf("failed getting Unit(%s) from template: %v", file, err) } } log.Debugf("Found Unit(%s)", name) return uf, nil }
// lazyCreateUnits iterates over a set of unit names and, for each, attempts to // ensure that a unit by that name exists in the Registry, by checking a number // of conditions and acting on the first one that succeeds, in order of: // 1. a unit by that name already existing in the Registry // 2. a unit file by that name existing on disk // 3. a corresponding unit template (if applicable) existing in the Registry // 4. a corresponding unit template (if applicable) existing on disk // Any error encountered during these steps is returned immediately (i.e. // subsequent Jobs are not acted on). An error is also returned if none of the // above conditions match a given Job. func lazyCreateUnits(args []string) error { for _, arg := range args { // TODO(jonboulle): this loop is getting too unwieldy; factor it out arg = maybeAppendDefaultUnitType(arg) name := unitNameMangle(arg) // First, check if there already exists a Unit by the given name in the Registry u, err := cAPI.Unit(name) if err != nil { return fmt.Errorf("error retrieving Unit(%s) from Registry: %v", name, err) } if u != nil { log.Debugf("Found Unit(%s) in Registry, no need to recreate it", name) warnOnDifferentLocalUnit(arg, u) continue } // Failing that, assume the name references a local unit file on disk, and attempt to load that, if it exists if _, err := os.Stat(arg); !os.IsNotExist(err) { unit, err := getUnitFromFile(arg) if err != nil { return fmt.Errorf("failed getting Unit(%s) from file: %v", arg, err) } u, err = createUnit(name, unit) if err != nil { return err } continue } // Otherwise (if the unit file does not exist), check if the name appears to be an instance unit, // and if so, check for a corresponding template unit in the Registry uni := unit.NewUnitNameInfo(name) if uni == nil { return fmt.Errorf("error extracting information from unit name %s", name) } else if !uni.IsInstance() { return fmt.Errorf("unable to find Unit(%s) in Registry or on filesystem", name) } tmpl, err := cAPI.Unit(uni.Template) if err != nil { return fmt.Errorf("error retrieving template Unit(%s) from Registry: %v", uni.Template, err) } // Finally, if we could not find a template unit in the Registry, check the local disk for one instead var uf *unit.UnitFile if tmpl == nil { file := path.Join(path.Dir(arg), uni.Template) if _, err := os.Stat(file); os.IsNotExist(err) { return fmt.Errorf("unable to find Unit(%s) or template Unit(%s) in Registry or on filesystem", name, uni.Template) } uf, err = getUnitFromFile(file) if err != nil { return fmt.Errorf("failed getting template Unit(%s) from file: %v", uni.Template, err) } } else { warnOnDifferentLocalUnit(arg, tmpl) uf = schema.MapSchemaUnitOptionsToUnitFile(tmpl.Options) } // If we found a template unit, create a near-identical instance unit in // the Registry - same unit file as the template, but different name u, err = createUnit(name, uf) if err != nil { return err } } return nil }
// lazyCreateJobs iterates over a set of Job names and, for each, attempts to // ensure that a Job by that name exists in the Registry, by checking a number // of conditions and acting on the first one that succeeds, in order of: // 1. a Job by that name already existing in the Registry // 2. a unit file by that name existing on disk // 3. a corresponding unit template (if applicable) existing in the Registry // 4. a corresponding unit template (if applicable) existing on disk // Any error encountered during these steps is returned immediately (i.e. // subsequent Jobs are not acted on). An error is also returned if none of the // above conditions match a given Job. // If signAndVerify is true, the Job will be signed (if it is created), or have // its signature verified if it already exists in the Registry. func lazyCreateJobs(args []string, signAndVerify bool) error { for _, arg := range args { // TODO(jonboulle): this loop is getting too unwieldy; factor it out jobName := unitNameMangle(arg) // First, check if there already exists a Job by the given name in the Registry j, err := cAPI.Job(jobName) if err != nil { return fmt.Errorf("error retrieving Job(%s) from Registry: %v", jobName, err) } if j != nil { log.V(1).Infof("Found Job(%s) in Registry, no need to recreate it", jobName) warnOnDifferentLocalUnit(arg, j) if signAndVerify { if err := verifyJob(j); err != nil { return err } } continue } // Failing that, assume the name references a local unit file on disk, and attempt to load that, if it exists if _, err := os.Stat(arg); !os.IsNotExist(err) { unit, err := getUnitFromFile(arg) if err != nil { return fmt.Errorf("failed getting Unit(%s) from file: %v", jobName, err) } j, err = createJob(jobName, unit) if err != nil { return err } if signAndVerify { if err := signJob(j); err != nil { return err } } continue } // Otherwise (if the unit file does not exist), check if the name appears to be an instance unit, // and if so, check for a corresponding template unit in the Registry uni := unit.NewUnitNameInfo(jobName) if uni == nil { return fmt.Errorf("error extracting information from unit name %s", jobName) } else if !uni.IsInstance() { return fmt.Errorf("unable to find Unit(%s) in Registry or on filesystem", jobName) } tmpl, err := cAPI.Job(uni.Template) if err != nil { return fmt.Errorf("error retrieving template Job(%s) from Registry: %v", uni.Template, err) } // Finally, if we could not find a template unit in the Registry, check the local disk for one instead var u *unit.Unit if tmpl == nil { file := path.Join(path.Dir(arg), uni.Template) if _, err := os.Stat(file); os.IsNotExist(err) { return fmt.Errorf("unable to find Unit(%s) or template Unit(%s) in Registry or on filesystem", jobName, uni.Template) } u, err = getUnitFromFile(file) if err != nil { return fmt.Errorf("failed getting template Unit(%s) from file: %v", uni.Template, err) } } else { warnOnDifferentLocalUnit(arg, tmpl) u = &tmpl.Unit } // If we found a template Unit or Job, create a near-identical instance Job in // the Registry - same Unit as the template, but different name j, err = createJob(jobName, u) if err != nil { return err } if signAndVerify { if err := signJob(j); err != nil { return err } } } return nil }
func (ur *unitsResource) set(rw http.ResponseWriter, req *http.Request, item string) { if err := validateContentType(req); err != nil { sendError(rw, http.StatusUnsupportedMediaType, err) return } var su schema.Unit dec := json.NewDecoder(req.Body) err := dec.Decode(&su) if err != nil { sendError(rw, http.StatusBadRequest, fmt.Errorf("unable to decode body: %v", err)) return } if su.Name == "" { su.Name = item } if item != su.Name { sendError(rw, http.StatusBadRequest, fmt.Errorf("name in URL %q differs from unit name in request body %q", item, su.Name)) return } if err := ValidateName(su.Name); err != nil { sendError(rw, http.StatusBadRequest, err) return } eu, err := ur.cAPI.Unit(su.Name) if err != nil { log.Errorf("Failed fetching Unit(%s) from Registry: %v", su.Name, err) sendError(rw, http.StatusInternalServerError, nil) return } newUnit := false if eu == nil { if len(su.Options) == 0 { err := errors.New("unit does not exist and options field empty") sendError(rw, http.StatusConflict, err) return } else if err := ValidateOptions(su.Options); err != nil { sendError(rw, http.StatusBadRequest, err) return } else { // New valid unit newUnit = true } } else if eu.Name == su.Name && len(su.Options) > 0 { // There is already a unit with the same name that // was submitted before. Check their hashes, if they do // not match then this is probably a new version which // needs its own new unit entry. // In the other case if su.Options == 0 then probably we // don't want to update the Unit options nor its content // but only set the target job state of the // corresponding unit, in this case just ignore. a := schema.MapSchemaUnitOptionsToUnitFile(su.Options) b := schema.MapSchemaUnitOptionsToUnitFile(eu.Options) newUnit = !unit.MatchUnitFiles(a, b) } if newUnit { ur.create(rw, su.Name, &su) return } if len(su.DesiredState) == 0 { err := errors.New("must provide DesiredState to update existing unit") sendError(rw, http.StatusConflict, err) return } un := unit.NewUnitNameInfo(su.Name) if un.IsTemplate() && job.JobState(su.DesiredState) != job.JobStateInactive { err := fmt.Errorf("cannot activate template %q", su.Name) sendError(rw, http.StatusBadRequest, err) return } ur.update(rw, su.Name, su.DesiredState) }
// lazyCreateUnits iterates over a set of unit names and, for each, attempts to // ensure that a unit by that name exists in the Registry, by checking a number // of conditions and acting on the first one that succeeds, in order of: // 1. a unit by that name already existing in the Registry // 2. a unit file by that name existing on disk // 3. a corresponding unit template (if applicable) existing in the Registry // 4. a corresponding unit template (if applicable) existing on disk // Any error encountered during these steps is returned immediately (i.e. // subsequent Jobs are not acted on). An error is also returned if none of the // above conditions match a given Job. func lazyCreateUnits(args []string) error { errchan := make(chan error) var wg sync.WaitGroup for _, arg := range args { // TODO(jonboulle): this loop is getting too unwieldy; factor it out arg = maybeAppendDefaultUnitType(arg) name := unitNameMangle(arg) // First, check if there already exists a Unit by the given name in the Registry u, err := cAPI.Unit(name) if err != nil { return fmt.Errorf("error retrieving Unit(%s) from Registry: %v", name, err) } if u != nil { log.Debugf("Found Unit(%s) in Registry, no need to recreate it", name) warnOnDifferentLocalUnit(arg, u) continue } var uf *unit.UnitFile // Failing that, assume the name references a local unit file on disk, and attempt to load that, if it exists // TODO(mischief): consolidate these two near-identical codepaths if _, err := os.Stat(arg); !os.IsNotExist(err) { uf, err = getUnitFromFile(arg) if err != nil { return fmt.Errorf("failed getting Unit(%s) from file: %v", arg, err) } } else { // Otherwise (if the unit file does not exist), check if the name appears to be an instance unit, // and if so, check for a corresponding template unit in the Registry uni := unit.NewUnitNameInfo(name) if uni == nil { return fmt.Errorf("error extracting information from unit name %s", name) } else if !uni.IsInstance() { return fmt.Errorf("unable to find Unit(%s) in Registry or on filesystem", name) } tmpl, err := cAPI.Unit(uni.Template) if err != nil { return fmt.Errorf("error retrieving template Unit(%s) from Registry: %v", uni.Template, err) } // Finally, if we could not find a template unit in the Registry, check the local disk for one instead if tmpl == nil { file := path.Join(path.Dir(arg), uni.Template) if _, err := os.Stat(file); os.IsNotExist(err) { return fmt.Errorf("unable to find Unit(%s) or template Unit(%s) in Registry or on filesystem", name, uni.Template) } uf, err = getUnitFromFile(file) if err != nil { return fmt.Errorf("failed getting template Unit(%s) from file: %v", uni.Template, err) } } else { warnOnDifferentLocalUnit(arg, tmpl) uf = schema.MapSchemaUnitOptionsToUnitFile(tmpl.Options) } // If we found a template unit, create a near-identical instance unit in // the Registry - same unit file as the template, but different name } _, err = createUnit(name, uf) if err != nil { return err } wg.Add(1) go checkUnitState(name, job.JobStateInactive, sharedFlags.BlockAttempts, os.Stdout, &wg, errchan) } go func() { wg.Wait() close(errchan) }() haserr := false for msg := range errchan { stderr("Error waiting on unit creation: %v", msg) haserr = true } if haserr { return fmt.Errorf("One or more errors creating units") } return nil }