// Mgmt // Copyright (C) 2013-2019+ James Shubin and the project contributors // Written by James Shubin and the project contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . package lib import ( "context" "fmt" "log" "os" "github.com/purpleidea/mgmt/etcd/client" "github.com/purpleidea/mgmt/etcd/deployer" etcdfs "github.com/purpleidea/mgmt/etcd/fs" "github.com/purpleidea/mgmt/gapi" "github.com/purpleidea/mgmt/util/errwrap" "github.com/pborman/uuid" "github.com/urfave/cli" git "gopkg.in/src-d/go-git.v4" ) const ( // MetadataPrefix is the etcd prefix where all our fs superblocks live. MetadataPrefix = "/fs" // StoragePrefix is the etcd prefix where all our fs data lives. StoragePrefix = "/storage" ) // deploy is the cli target to manage deploys to our cluster. // TODO: add a timeout and/or cancel signal to replace context.TODO() func deploy(c *cli.Context, name string, gapiObj gapi.GAPI) error { cliContext := c.Parent() if cliContext == nil { return fmt.Errorf("could not get cli context") } program, version := safeProgram(c.App.Name), c.App.Version var flags Flags var debug bool if val, exists := c.App.Metadata["flags"]; exists { if f, ok := val.(Flags); ok { flags = f debug = flags.Debug } } Logf := func(format string, v ...interface{}) { log.Printf("deploy: "+format, v...) } hello(program, version, flags) // say hello! defer Logf("goodbye!") var hash, pHash string if !cliContext.Bool("no-git") { wd, err := os.Getwd() if err != nil { return errwrap.Wrapf(err, "could not get current working directory") } repo, err := git.PlainOpen(wd) if err != nil { return errwrap.Wrapf(err, "could not open git repo") } head, err := repo.Head() if err != nil { return errwrap.Wrapf(err, "could not read git HEAD") } hash = head.Hash().String() // current commit id Logf("hash: %s", hash) lo := &git.LogOptions{ From: head.Hash(), } commits, err := repo.Log(lo) if err != nil { return errwrap.Wrapf(err, "could not read git log") } if _, err := commits.Next(); err != nil { // skip over HEAD return errwrap.Wrapf(err, "could not read HEAD in git log") // weird! } commit, err := commits.Next() if err == nil { // errors are okay, we might be empty pHash = commit.Hash.String() // previous commit id } Logf("previous deploy hash: %s", pHash) if cliContext.Bool("force") { pHash = "" // don't check this :( } if hash == "" { return errwrap.Wrapf(err, "could not get git deploy hash") } } uniqueid := uuid.New() // panic's if it can't generate one :P etcdClient := client.NewClientFromSeedsNamespace( cliContext.StringSlice("seeds"), // endpoints NS, ) if err := etcdClient.Init(); err != nil { return errwrap.Wrapf(err, "client Init failed") } defer func() { err := errwrap.Wrapf(etcdClient.Close(), "client Close failed") if err != nil { // TODO: cause the final exit code to be non-zero Logf("client cleanup error: %+v", err) } }() simpleDeploy := &deployer.SimpleDeploy{ Client: etcdClient, Debug: debug, Logf: func(format string, v ...interface{}) { Logf("deploy: "+format, v...) }, } if err := simpleDeploy.Init(); err != nil { return errwrap.Wrapf(err, "deploy Init failed") } defer func() { err := errwrap.Wrapf(simpleDeploy.Close(), "deploy Close failed") if err != nil { // TODO: cause the final exit code to be non-zero Logf("deploy cleanup error: %+v", err) } }() // get max id (from all the previous deploys) max, err := simpleDeploy.GetMaxDeployID(context.TODO()) if err != nil { return errwrap.Wrapf(err, "error getting max deploy id") } // find the latest id var id = max + 1 // next id Logf("previous max deploy id: %d", max) etcdFs := &etcdfs.Fs{ Client: etcdClient, // TODO: using a uuid is meant as a temporary measure, i hate them Metadata: MetadataPrefix + fmt.Sprintf("/deploy/%d-%s", id, uniqueid), DataPrefix: StoragePrefix, Debug: debug, Logf: func(format string, v ...interface{}) { Logf("fs: "+format, v...) }, } cliInfo := &gapi.CliInfo{ CliContext: c, // don't pass in the parent context Fs: etcdFs, Debug: debug, Logf: func(format string, v ...interface{}) { // TODO: is this a sane prefix to use here? log.Printf("cli: "+format, v...) }, } deploy, err := gapiObj.Cli(cliInfo) if err != nil { return errwrap.Wrapf(err, "cli parse error") } if deploy == nil { // not used return fmt.Errorf("not enough information specified") } // redundant deploy.Noop = cliContext.Bool("noop") deploy.Sema = cliContext.Int("sema") str, err := deploy.ToB64() if err != nil { return errwrap.Wrapf(err, "encoding error") } // this nominally checks the previous git hash matches our expectation if err := simpleDeploy.AddDeploy(context.TODO(), id, hash, pHash, &str); err != nil { return errwrap.Wrapf(err, "could not create deploy id `%d`", id) } Logf("success, id: %d", id) return nil }