func main() { app := cli.NewApp() app.Name = "rancher-meta-template" app.Version = fmt.Sprintf("%s-%s", AppVersion, Revision) app.Usage = "A rancher-metadata config writer." app.Commands = []cli.Command{ cli.Command{ Name: "run", Usage: "run config generation", Flags: []cli.Flag{ cli.StringFlag{Name: "config, c", Value: "/etc/rancher-meta-template/config.toml", Usage: "Configuration File path"}, cli.IntFlag{Name: "repeat, r", Value: 60, Usage: "Repeat config creation every x seconds", EnvVar: "RANCHER_META_REPEAT"}, cli.StringFlag{Name: "host, H", Value: "http://rancher-metadata", Usage: "Rancher metadata host", EnvVar: "RANCHER_META_HOST"}, cli.StringFlag{Name: "template, t", Usage: "template path", EnvVar: "RANCHER_META_TEMPLATE_PATH"}, cli.StringFlag{Name: "prefix, p", Value: "/latest", Usage: "api prefix", EnvVar: "RANCHER_META_PREFIX"}, cli.StringFlag{Name: "destination, d", Usage: "the destination path", EnvVar: "RANCHER_META_DEST_PATH"}, cli.StringFlag{Name: "user, u", Value: "nouser", Usage: "run as user", EnvVar: "RANCHER_META_USER"}, cli.StringFlag{Name: "group, g", Value: "nogroup", Usage: "run as group", EnvVar: "RANCHER_META_GROUP"}, cli.StringFlag{Name: "loglevel, l", Value: "warning", Usage: "the loglevel", EnvVar: "RANCHER_META_LOGLEVEL"}, }, Action: func(ctx *cli.Context) { logging.Info("startup") var cnf *config.Config cnf, _ = config.NewFromFile(ctx.String("config")) if cnf == nil { logging.Warning("config file not found, get config from cli context") c, err := config.NewFromCtx(ctx) if err != nil { logging.Error(errors.Annotate(err, "new config from context")) return } cnf = c } else { cnf.OverrideFromCtx(ctx) } cnf.Print() if err := cnf.Validate(); err != nil { logging.Error(errors.Annotate(err, "validate config")) return } if err := logging.SetLogLevel(cnf.LogLevel); err != nil { logging.Error(errors.Annotate(err, "set log level")) return } if err := processTemplates(cnf); err != nil { logging.Error(errors.Annotate(err, "process templates")) } }, }, } app.RunAndExitOnError() }
func processTemplates(cnf *config.Config) error { apiURL := cnf.Host + cnf.Prefix meta, err := metadata.NewClientAndWait(apiURL) if err != nil { return errors.Annotate(err, "get meta client") } logging.Info("connect rancher metadata url: %q", apiURL) //expand template paths logging.Debug("expand template paths") for idx, set := range cnf.Sets { if !path.IsAbs(set.TemplatePath) { cnf.Sets[idx].TemplatePath = path.Join(DEFAULT_TEMPLATE_DIR, set.TemplatePath) } } version := "init" for { newVersion, err := meta.GetVersion() if err != nil { return errors.Annotate(err, "get version") } if newVersion == version { time.Sleep(5 * time.Second) continue } version = newVersion logging.Info("metadata changed - refresh config") for _, set := range cnf.Sets { scratch.Reset() if err := processTemplateSet(meta, set); err != nil { return errors.Annotate(err, "process template set") } } time.Sleep(time.Duration(cnf.Repeat) * time.Second) } return nil }
func (cnf *Config) Print() { logging.Info("check every %d seconds", cnf.Repeat) logging.Info("metadata host: %s", cnf.Host) logging.Info("prefix is: %s", cnf.Prefix) logging.Info("loglevel is: %s", cnf.LogLevel) logging.Info("run as %s:%s", cnf.User, cnf.Group) logging.Info(" %d template sets found", len(cnf.Sets)) }
func processTemplateSet(meta *metadata.Client, set config.TemplateSet) error { if _, err := os.Stat(set.TemplatePath); err != nil { logging.Warning("template path %q is not available: skip", set.TemplatePath) return nil } buf, err := ioutil.ReadFile(set.TemplatePath) if err != nil { return errors.Annotate(err, "read template file") } templ := template.New(set.Name).Funcs(newFuncMap()) tmpl, err := templ.Parse(string(buf)) if err != nil { return errors.Annotate(err, "parse template") } lastMd5 := "" if _, err = os.Stat(set.DestinationPath); err == nil { lmd5, err := computeMd5(set.DestinationPath) if err != nil { return errors.Annotate(err, "get last md5 hash") } lastMd5 = lmd5 } ctx, err := createTemplateCtx(meta) if err != nil { return errors.Annotate(err, "create template context") } var newBuf bytes.Buffer wr := bufio.NewWriter(&newBuf) if err := tmpl.Execute(wr, ctx); err != nil { return errors.Annotate(err, "execute template") } if err := wr.Flush(); err != nil { return errors.Annotate(err, "flush tmpl writer") } hash := md5.New() hash.Write(newBuf.Bytes()) currentMd5 := fmt.Sprintf("%x", hash.Sum(nil)) if lastMd5 == currentMd5 { return nil } if lastMd5 == "" { logging.Info("create output file") } else { logging.Info("last md5 sum is %q", lastMd5) logging.Info("current md5 sum is %q", currentMd5) logging.Info("output file needs refresh") } f, err := os.Create(set.DestinationPath) if err != nil { return errors.Annotate(err, "create destination file") } w := bufio.NewWriter(f) if _, err := w.Write(newBuf.Bytes()); err != nil { return errors.Annotate(err, "write to output") } if err := w.Flush(); err != nil { return errors.Annotate(err, "flush out writer") } if err := f.Close(); err != nil { return errors.Annotate(err, "outfile close") } logging.Info("process check & generate") pipes := make([]pipe.Pipe, 0) pipes = appendCommandPipe(set.Check, pipes) pipes = appendCommandPipe(set.Run, pipes) script := pipe.Script(pipes...) if output, err := pipe.CombinedOutput(script); err != nil { logging.Info(string(output)) return errors.Annotate(err, "check & run") } return nil }