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 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
}