func (d *deployer) deployUnit(wantedUnit *schema.Unit) error { currentUnit, err := d.fleetapi.Unit(wantedUnit.Name) if err != nil { return err } if currentUnit == nil { err := d.fleetapi.CreateUnit(wantedUnit) if err != nil { return err } return nil } wuf := schema.MapSchemaUnitOptionsToUnitFile(wantedUnit.Options) cuf := schema.MapSchemaUnitOptionsToUnitFile(currentUnit.Options) if wuf.Hash() != cuf.Hash() { log.Printf("INFO Service %s differs from the cluster version", wantedUnit.Name) wantedUnit.DesiredState = "inactive" err = d.fleetapi.DestroyUnit(wantedUnit.Name) if err != nil { return err } err = d.fleetapi.CreateUnit(wantedUnit) if err != nil { return err } } return nil }
func runCatUnit(args []string) (exit int) { if len(args) != 1 { stderr("One unit file must be provided") return 1 } name := unitNameMangle(args[0]) u, err := cAPI.Unit(name) if err != nil { stderr("Error retrieving Unit %s: %v", name, err) return 1 } if u == nil { stderr("Unit %s not found", name) return 1 } uf := schema.MapSchemaUnitOptionsToUnitFile(u.Options) // Must not add a newline here. The contents of the unit file // must not be modified. fmt.Print(uf.String()) return }
// getUnitFileFromTemplate attempts to get a Unit from a template unit that // is either in the registry or on the file system // It takes two arguments, the template information and the unit file name // It returns the Unit or nil; and any error encountered func getUnitFileFromTemplate(cCmd *cobra.Command, uni *unit.UnitNameInfo, fileName string) (*unit.UnitFile, error) { var uf *unit.UnitFile tmpl, err := cAPI.Unit(uni.Template) if err != nil { return nil, fmt.Errorf("error retrieving template Unit(%s) from Registry: %v", uni.Template, err) } if tmpl != nil { isLocalUnitDifferent(cCmd, fileName, tmpl, false) uf = schema.MapSchemaUnitOptionsToUnitFile(tmpl.Options) log.Debugf("Template Unit(%s) found in registry", uni.Template) } else { // Finally, if we could not find a template unit in the Registry, // check the local disk for one instead filePath := path.Join(path.Dir(fileName), uni.Template) if _, err := os.Stat(filePath); os.IsNotExist(err) { return nil, fmt.Errorf("unable to find template Unit(%s) in Registry or on filesystem", uni.Template) } uf, err = getUnitFromFile(filePath) if err != nil { return nil, fmt.Errorf("unable to load template Unit(%s) from file: %v", uni.Template, err) } } return uf, nil }
// ValidateOptions ensures that a set of UnitOptions is valid; if not, an error // is returned detailing the issue encountered. If there are several problems // with a set of options, only the first is returned. func ValidateOptions(opts []*schema.UnitOption) error { uf := schema.MapSchemaUnitOptionsToUnitFile(opts) // Sanity check using go-systemd's deserializer, which will do things // like check for excessive line lengths _, err := gsunit.Deserialize(gsunit.Serialize(uf.Options)) if err != nil { return err } j := &job.Job{ Unit: *uf, } conflicts := pkg.NewUnsafeSet(j.Conflicts()...) replaces := pkg.NewUnsafeSet(j.Replaces()...) peers := pkg.NewUnsafeSet(j.Peers()...) for _, peer := range peers.Values() { for _, conflict := range conflicts.Values() { matched, _ := path.Match(conflict, peer) if matched { return fmt.Errorf("unresolvable requirements: peer %q matches conflict %q", peer, conflict) } } for _, replace := range replaces.Values() { matched, _ := path.Match(replace, peer) if matched { return fmt.Errorf("unresolvable requirements: peer %q matches replace %q", peer, replace) } } } hasPeers := peers.Length() != 0 hasConflicts := conflicts.Length() != 0 hasReplaces := replaces.Length() != 0 _, hasReqTarget := j.RequiredTarget() u := &job.Unit{ Unit: *uf, } isGlobal := u.IsGlobal() switch { case hasReqTarget && hasPeers: return errors.New("MachineID cannot be used with Peers") case hasReqTarget && hasConflicts: return errors.New("MachineID cannot be used with Conflicts") case hasReqTarget && isGlobal: return errors.New("MachineID cannot be used with Global") case hasReqTarget && hasReplaces: return errors.New("MachineID cannot be used with Replaces") case isGlobal && hasPeers: return errors.New("Global cannot be used with Peers") case isGlobal && hasReplaces: return errors.New("Global cannot be used with Replaces") case hasConflicts && hasReplaces: return errors.New("Conflicts cannot be used with Replaces") } return nil }
// matchLocalFileAndUnit compares a file with a Unit // Returns true if the contents of the file matches the unit one, false // otherwise; and any error encountered. func matchLocalFileAndUnit(file string, su *schema.Unit) (bool, error) { result := false a := schema.MapSchemaUnitOptionsToUnitFile(su.Options) _, err := os.Stat(file) if err == nil { b, err := getUnitFromFile(file) if err == nil { result = unit.MatchUnitFiles(a, b) } } return result, err }
// matchLocalFileAndUnit compares a file with a Unit // Returns true if the contents of the file matches the unit one, false // otherwise; and any error encountered. func matchLocalFileAndUnit(file string, su *schema.Unit) (bool, error) { a := schema.MapSchemaUnitOptionsToUnitFile(su.Options) _, err := os.Stat(file) if err != nil { return false, err } b, err := getUnitFromFile(file) if err != nil { return false, err } return unit.MatchUnitFiles(a, b), nil }
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 (f *FleetTunnel) Cat(unitName string) (string, error) { log.Debugf("cat unit %v", unitName) var u *schema.Unit op := func() error { var err error u, err = f.cAPI.Unit(unitName) return maskAny(err) } if err := backoff.Retry(op, backoff.NewExponentialBackOff()); err != nil { return "", maskAny(err) } if u == nil { return "", maskAny(errgo.WithCausef(nil, NotFoundError, unitName)) } uf := schema.MapSchemaUnitOptionsToUnitFile(u.Options) return uf.String(), nil }
func runCatUnit(args []string) (exit int) { if len(args) != 1 { fmt.Fprintln(os.Stderr, "One unit file must be provided.") return 1 } name := unitNameMangle(args[0]) u, err := cAPI.Unit(name) if err != nil { fmt.Fprintf(os.Stderr, "Error retrieving Unit %s: %v\n", name, err) return 1 } if u == nil { fmt.Fprintf(os.Stderr, "Unit %s not found.\n", name) return 1 } uf := schema.MapSchemaUnitOptionsToUnitFile(u.Options) fmt.Print(uf.String()) return }
func appendJobsForTests(jobs *[]job.Job, machine machine.MachineState, prefix string, unitCount int, template bool) { if template { // for start or load operations we may need to wait // during the creation of units, and since this is a // faked registry just set the 'Global' flag so we don't // block forever Options := []*schema.UnitOption{ &schema.UnitOption{ Section: "Unit", Name: "Description", Value: fmt.Sprintf("Template %[email protected]", prefix), }, &schema.UnitOption{ Section: "X-Fleet", Name: "Global", Value: "true", }, } uf := schema.MapSchemaUnitOptionsToUnitFile(Options) j := job.Job{ Name: fmt.Sprintf("%[email protected]", prefix), Unit: *uf, TargetMachineID: machine.ID, } *jobs = append(*jobs, j) } else { for i := 1; i <= unitCount; i++ { j := job.Job{ Name: fmt.Sprintf("%s%d.service", prefix, i), Unit: unit.UnitFile{}, TargetMachineID: machine.ID, } *jobs = append(*jobs, j) } } return }
// ValidateOptions ensures that a set of UnitOptions is valid; if not, an error // is returned detailing the issue encountered. If there are several problems // with a set of options, only the first is returned. func ValidateOptions(opts []*schema.UnitOption) error { uf := schema.MapSchemaUnitOptionsToUnitFile(opts) j := &job.Job{ Unit: *uf, } conflicts := pkg.NewUnsafeSet(j.Conflicts()...) peers := pkg.NewUnsafeSet(j.Peers()...) for _, peer := range peers.Values() { for _, conflict := range conflicts.Values() { matched, _ := path.Match(conflict, peer) if matched { return fmt.Errorf("unresolvable requirements: peer %q matches conflict %q", peer, conflict) } } } hasPeers := peers.Length() != 0 hasConflicts := conflicts.Length() != 0 _, hasReqTarget := j.RequiredTarget() u := &job.Unit{ Unit: *uf, } isGlobal := u.IsGlobal() switch { case hasReqTarget && hasPeers: return errors.New("MachineID cannot be used with Peers") case hasReqTarget && hasConflicts: return errors.New("MachineID cannot be used with Conflicts") case hasReqTarget && isGlobal: return errors.New("MachineID cannot be used with Global") case isGlobal && hasPeers: return errors.New("Global cannot be used with Peers") case isGlobal && hasConflicts: return errors.New("Global cannot be used with Conflicts") } return nil }
"dstate": func(u schema.Unit, full bool) string { if u.DesiredState == "" { return "-" } return u.DesiredState }, "target": mapTargetField, "tmachine": mapTargetField, "state": func(u schema.Unit, full bool) string { if suToGlobal(u) || u.CurrentState == "" { return "-" } return u.CurrentState }, "hash": func(u schema.Unit, full bool) string { uf := schema.MapSchemaUnitOptionsToUnitFile(u.Options) if !full { return uf.Hash().Short() } return uf.Hash().String() }, "desc": func(u schema.Unit, full bool) string { uf := schema.MapSchemaUnitOptionsToUnitFile(u.Options) d := uf.Description() if d == "" { return "-" } return d }, } )
// 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 }
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 }