// ParallelBuild runs multiple docker builds at the same time. // // Params: // -images ([]BuildImg): Images to build // -alwaysFetch (bool): Default false. If set to true, this will always fetch // the Docker image even if it already exists in the registry. // // Returns: // // - Waiter: A *sync.WaitGroup that is waiting for the docker downloads to finish. // // Context: // // This puts 'ParallelBuild.failN" (int) into the context to indicate how many failures // occurred during fetches. func ParallelBuild(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { images := p.Get("images", []BuildImg{}).([]BuildImg) var wg sync.WaitGroup var m sync.Mutex var fails int for _, img := range images { img := img wg.Add(1) safely.GoDo(c, func() { log.Infof(c, "Starting build for %s (tag: %s)", img.Path, img.Tag) if _, err := buildImg(c, img.Path, img.Tag); err != nil { log.Errf(c, "Failed to build docker image: %s", err) m.Lock() fails++ m.Unlock() } wg.Done() }) } // Number of failures. c.Put("ParallelBuild.failN", fails) return &wg, nil }
// ParallelBuild runs multiple docker builds at the same time. // // Params: // -images ([]BuildImg): Images to build // -alwaysFetch (bool): Default false. If set to true, this will always fetch // the Docker image even if it already exists in the registry. // // Returns: // // - Waiter: A *sync.WaitGroup that is waiting for the docker downloads to finish. // // Context: // // This puts 'ParallelBuild.failN" (int) into the context to indicate how many failures // occurred during fetches. func ParallelBuild(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { images := p.Get("images", []BuildImg{}).([]BuildImg) var wg sync.WaitGroup var m sync.Mutex var fails int for _, img := range images { img := img // HACK: ensure "docker build" is serialized by allowing only one entry in // the WaitGroup. This works around the "simultaneous docker pull" bug. wg.Wait() wg.Add(1) safely.GoDo(c, func() { log.Infof(c, "Starting build for %s (tag: %s)", img.Path, img.Tag) if _, err := buildImg(c, img.Path, img.Tag); err != nil { log.Errf(c, "Failed to build docker image: %s", err) m.Lock() fails++ m.Unlock() } wg.Done() }) } // Number of failures. c.Put("ParallelBuild.failN", fails) return &wg, nil }
// Serve starts a native SSH server. // // The general design of the server is that it acts as a main server for // a Cookoo app. It assumes that certain things have been configured for it, // like an ssh.ServerConfig. Once it runs, it will block until the main // process terminates. If you want to stop it prior to that, you can grab // the closer ("sshd.Closer") out of the context and send it a signal. // // Currently, the service is not generic. It only runs git hooks. // // This expects the following Context variables. // - ssh.Hostkeys ([]ssh.Signer): Host key, as an unparsed byte slice. // - ssh.Address (string): Address/port // - ssh.ServerConfig (*ssh.ServerConfig): The server config to use. // // This puts the following variables into the context: // - ssh.Closer (chan interface{}): Send a message to this to shutdown the server. func Serve(reg *cookoo.Registry, router *cookoo.Router, c cookoo.Context) cookoo.Interrupt { hostkeys := c.Get(HostKeys, []ssh.Signer{}).([]ssh.Signer) addr := c.Get(Address, "0.0.0.0:2223").(string) cfg := c.Get(ServerConfig, &ssh.ServerConfig{}).(*ssh.ServerConfig) for _, hk := range hostkeys { cfg.AddHostKey(hk) log.Infof(c, "Added hostkey.") } listener, err := net.Listen("tcp", addr) if err != nil { return err } srv := &server{ c: c, gitHome: "/home/git", } closer := make(chan interface{}, 1) c.Put("sshd.Closer", closer) log.Infof(c, "Listening on %s", addr) srv.listen(listener, cfg, closer) return nil }
// iam injects info into the environment about a host's self. // // Sets the following environment variables. (Values represent the data format. // Instances will get its own values.) // // MY_NODEIP=10.245.1.3 // MY_SERVICE_IP=10.22.1.4 // MY_PORT_PEER=2380 // MY_PORT_CLIENT=2379 // MY_NAMESPACE=default // MY_SELFLINK=/api/v1/namespaces/default/pods/deis-etcd-1-336jp // MY_UID=62a3b54a-6956-11e5-b8ab-0800279dd272 // MY_APISERVER=https://10.247.0.1:443 // MY_NAME=deis-etcd-1-336jp // MY_IP=10.246.44.7 // MY_LABEL_NAME=deis-etcd-1 # One entry per label in the JSON // MY_ANNOTATION_NAME=deis-etcd-1 # One entry per annitation in the JSON // MY_PORT_CLIENT=4100 // MY_PORT_PEER=2380 func iam(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { me, err := aboutme.FromEnv() if err != nil { log.Errf(c, "Failed aboutme.FromEnv: %s", err) } else { if strings.TrimSpace(me.IP) == "" { log.Warn(c, "No IP found by API query.") ip, err := aboutme.MyIP() if err != nil || ip == "" { // Force pod death. log.Errf(c, "Failed to get an IP address: %s", err) os.Exit(5) } } me.ShuntEnv() os.Setenv("ETCD_NAME", me.Name) c.Put("ETCD_NAME", me.Name) } passEnv("MY_PORT_CLIENT", "$DEIS_ETCD_1_SERVICE_PORT_CLIENT") passEnv("MY_PORT_PEER", "$DEIS_ETCD_1_SERVICE_PORT_PEER") return nil, nil }
// ParseYaml parses the glide.yaml format and returns a Configuration object. // // Params: // - filename (string): YAML filename as a string // // Context: // - yaml.File: This puts the parsed YAML file into the context. // // Returns: // - *Config: The configuration. func ParseYaml(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { fname := p.Get("filename", "glide.yaml").(string) //conf := new(Config) f, err := yaml.ReadFile(fname) if err != nil { return nil, err } c.Put("yaml.File", f) return FromYaml(f.Root) }
func setupHandler(c *cli.Context, route string, cxt cookoo.Context, router *cookoo.Router) { cxt.Put("q", c.GlobalBool("quiet")) cxt.Put("debug", c.GlobalBool("debug")) cxt.Put("no-color", c.GlobalBool("no-color")) cxt.Put("yaml", c.GlobalString("yaml")) cxt.Put("home", c.GlobalString("home")) cxt.Put("cliArgs", c.Args()) if err := router.HandleRequest(route, cxt, false); err != nil { fmt.Printf("Oops! %s\n", err) os.Exit(1) } }
// iam injects info into the environment about a host's self. // // Sets the following environment variables. (Values represent the data format. // Instances will get its own values.) // // MY_NODEIP=10.245.1.3 // MY_SERVICE_IP=10.22.1.4 // MY_PORT_PEER=2380 // MY_PORT_CLIENT=2379 // MY_NAMESPACE=default // MY_SELFLINK=/api/v1/namespaces/default/pods/deis-etcd-1-336jp // MY_UID=62a3b54a-6956-11e5-b8ab-0800279dd272 // MY_APISERVER=https://10.247.0.1:443 // MY_NAME=deis-etcd-1-336jp // MY_IP=10.246.44.7 // MY_LABEL_NAME=deis-etcd-1 # One entry per label in the JSON // MY_ANNOTATION_NAME=deis-etcd-1 # One entry per annitation in the JSON // MY_PORT_CLIENT=4100 // MY_PORT_PEER=2380 func iam(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { me, err := aboutme.FromEnv() if err != nil { log.Errf(c, "Failed aboutme.FromEnv: %s", err) } else { me.ShuntEnv() os.Setenv("ETCD_NAME", me.Name) c.Put("ETCD_NAME", me.Name) } passEnv("MY_PORT_CLIENT", "$DEIS_ETCD_1_SERVICE_PORT_CLIENT") passEnv("MY_PORT_PEER", "$DEIS_ETCD_1_SERVICE_PORT_PEER") return nil, nil }
// Get gets one or more environment variables and puts them into the context. // // Parameters passed in are of the form varname => defaultValue. // // r.Route("foo", "example").Does(envvar.Get).Using("HOME").WithDefault(".") // // As with all environment variables, the default value must be a string. // // WARNING: Since parameters are a map, order of processing is not // guaranteed. If order is important, you'll need to call this command // multiple times. // // For each parameter (`Using` clause), this command will look into the // environment for a matching variable. If it finds one, it will add that // variable to the context. If it does not find one, it will expand the // default value (so you can set a default to something like "$HOST:$PORT") // and also put the (unexpanded) default value back into the context in case // any subsequent call to `os.Getenv` occurs. func Get(c cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) { for name, def := range params.AsMap() { var val string if val = os.Getenv(name); len(val) == 0 { def := def.(string) val = os.ExpandEnv(def) // We want to make sure that any subsequent calls to Getenv // return the same default. os.Setenv(name, val) } c.Put(name, val) log.Debugf(c, "Name: %s, Val: %s", name, val) } return true, nil }
// Configure creates a new SSH configuration object. // // Config sets a PublicKeyCallback handler that forwards public key auth // requests to the route named "pubkeyAuth". // // This assumes certain details about our environment, like the location of the // host keys. It also provides only key-based authentication. // ConfigureServerSshConfig // // Returns: // An *ssh.ServerConfig func Configure(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { router := c.Get("cookoo.Router", nil).(*cookoo.Router) cfg := &ssh.ServerConfig{ PublicKeyCallback: func(m ssh.ConnMetadata, k ssh.PublicKey) (*ssh.Permissions, error) { c.Put("metadata", m) c.Put("key", k) pubkeyAuth := c.Get("route.sshd.pubkeyAuth", "pubkeyAuth").(string) err := router.HandleRequest(pubkeyAuth, c, true) return c.Get("pubkeyAuth", &ssh.Permissions{}).(*ssh.Permissions), err }, } return cfg, nil }
// Set takes the given names and values and puts them into both the context // and the environment. // // Unlike Get, it does not try to retrieve the values from the environment // first. // // Values are passed through os.ExpandEnv() // // There is no guarantee of insertion order. If multiple name/value pairs // are given, they will be put into the context in whatever order they // are retrieved from the underlying map. // // Params: // accessed as map[string]string // Returns: // nothing, but inserts all name/value pairs into the context and the // environment. func Set(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { for name, def := range p.AsMap() { // Assume Nil means unset the value. if def == nil { def = "" } val := fmt.Sprintf("%v", def) val = os.ExpandEnv(val) log.Debugf(c, "Name: %s, Val: %s", name, val) os.Setenv(name, val) c.Put(name, val) } return true, nil }
func addFlagsToContext(flagset *flag.FlagSet, cxt cookoo.Context) { store := func(f *flag.Flag) { // fmt.Printf("Storing %s in context with value %s.\n", f.Name, f.Value.String()) // Basically, we can tell the difference between booleans and strings, and that's it. // Other types are a loss. /* if f.IsBoolFlag != nil { cxt.Put(f.Name, f.Value.String() == "true") } else { cxt.Put(f.Name, f.Value.String()) } */ cxt.Put(f.Name, f.Value.String()) } flagset.VisitAll(store) }
// AuthKey authenticates based on a public key. // // Params: // - metadata (ssh.ConnMetadata) // - key (ssh.PublicKey) // // Returns: // *ssh.Permissions // func AuthKey(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { log.Debugf(c, "Starting ssh authentication") key := p.Get("key", nil).(ssh.PublicKey) userInfo, err := controller.UserInfoFromKey(key) if err != nil { return nil, err } userInfo.Key = string(ssh.MarshalAuthorizedKey(key)) c.Put("userinfo", userInfo) log.Infof(c, "Key accepted for user %s.", userInfo.Username) perm := &ssh.Permissions{ Extensions: map[string]string{ "user": userInfo.Username, }, } return perm, nil }
// Shift the args N (default 1) times, returning the last shifted value. // // Params: // - n: The number of times to shift. Only the last value is returned. // - args: The name of the context slice/array to modify. This value will be retrieved // from the context. Default: "os.Args" func ShiftArgs(c cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) { n := params.Get("n", 1).(int) argName := params.Get("args", "os.Args").(string) args, ok := c.Get(argName, nil).([]string) if !ok { return nil, &cookoo.FatalError{"Could not get arg out of context: No such arg name."} } if len(args) < n { c.Put(argName, make([]string, 0)) //log.Printf("Not enough args in %s", argName) return nil, &cookoo.RecoverableError{"Not enough arguments."} } targetArg := n - 1 shifted := args[targetArg] c.Put(argName, args[n:]) return shifted, nil }
// Get gets one or more environment variables and puts them into the context. // // Parameters passed in are of the form varname => defaultValue. // // r.Route("foo", "example").Does(envvar.Get).Using("HOME").WithDefault(".") // // As with all environment variables, the default value must be a string. // // WARNING: Since parameters are a map, order of processing is not // guaranteed. If order is important, you'll need to call this command // multiple times. // // For each parameter (`Using` clause), this command will look into the // environment for a matching variable. If it finds one, it will add that // variable to the context. If it does not find one, it will expand the // default value (so you can set a default to something like "$HOST:$PORT") // and also put the (unexpanded) default value back into the context in case // any subsequent call to `os.Getenv` occurs. func Get(c cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) { for name, def := range params.AsMap() { var val string if val = os.Getenv(name); len(val) == 0 { if def == nil { def = "" } def, ok := def.(string) if !ok { log.Warnf(c, "Could not convert %s. Type is %T", name, def) } val = os.ExpandEnv(def) // We want to make sure that any subsequent calls to Getenv // return the same default. os.Setenv(name, val) } c.Put(name, val) log.Debugf(c, "Name: %s, Val: %s", name, val) } return true, nil }
// iam injects info into the environment about a host's self. // // Sets the following environment variables. (Values represent the data format. // Instances will get its own values.) // // MY_NODEIP=10.245.1.3 // MY_SERVICE_IP=10.22.1.4 // MY_PORT_PEER=2380 // MY_PORT_CLIENT=2379 // MY_NAMESPACE=default // MY_SELFLINK=/api/v1/namespaces/default/pods/deis-etcd-1-336jp // MY_UID=62a3b54a-6956-11e5-b8ab-0800279dd272 // MY_APISERVER=https://10.247.0.1:443 // MY_NAME=deis-etcd-1-336jp // MY_IP=10.246.44.7 // MY_LABEL_NAME=deis-etcd-1 # One entry per label in the JSON // MY_ANNOTATION_NAME=deis-etcd-1 # One entry per annitation in the JSON // MY_PORT_CLIENT=4100 // MY_PORT_PEER=2380 func iam(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { me, err := aboutme.FromEnv() if err != nil { log.Errf(c, "Failed aboutme.FromEnv: %s", err) log.Warn(c, "Attempting to recover.") } // This will try to recover whenever IP is not set. Only some fields // can be recovered. But all we really need is IP and Name. if strings.TrimSpace(me.IP) == "" { log.Warn(c, "No IP found by API query.") ip, err := aboutme.MyIP() if err != nil || ip == "" { // Force pod death. log.Errf(c, "Failed to get an IP address: %s", err) os.Exit(5) } me.IP = ip } if strings.TrimSpace(me.Name) == "" { // Try to set name from DAPI. me.Name = os.Getenv("POD_NAME") log.Warnf(c, "Setting name to %q", me.Name) } if strings.TrimSpace(me.Namespace) == "" { // Try to set namespace from DAPI. me.Namespace = os.Getenv("POD_NAMESPACE") log.Warnf(c, "Setting name to %q", me.Namespace) } me.ShuntEnv() os.Setenv("ETCD_NAME", me.Name) c.Put("ETCD_NAME", me.Name) passEnv("MY_PORT_CLIENT", "$DEIS_ETCD_1_SERVICE_PORT_CLIENT") passEnv("MY_PORT_PEER", "$DEIS_ETCD_1_SERVICE_PORT_PEER") return nil, nil }
func (r *RequestResolver) Resolve(path string, cxt cookoo.Context) (string, error) { // Parse out any flags. Maybe flag specs are in context? flagsetO, ok := cxt.Has("globalFlags") if !ok { // No args to parse. Just return path. return path, nil } flagset := flagsetO.(*flag.FlagSet) flagset.Parse(strings.Split(path, " ")) addFlagsToContext(flagset, cxt) args := flagset.Args() // This is a failure condition... Need to fix Cookoo to support error return. if len(args) == 0 { return path, &cookoo.RouteError{"Could not resolve route " + path} } // Put the rest of the args to the context. cxt.Put("args", args[1:]) // Parse argv[0] as subcommand return args[0], nil }
// GuessContentType guesses the MIME type of a given name. // // Name should be a path-like thing with an extension. E.g. foo.html, // foo/bar/baz.css // // If this detects a file with extensions like gz, zip, or Z, it will also // set the context `web.ContentEncoding` to the appropriate encoding. // // Params: // - name (string): The filename-like thing to use to guess the content type. // Returns: // string content-type func GuessContentType(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { n := p.Get("name", "").(string) ext := strings.ToLower(path.Ext(n)) switch ext { case ".Z": c.Put(ContentEncoding, "compress") case ".gz", ".gzip", ".tgz": c.Put(ContentEncoding, "gzip") if ext == "tgz" { ext = "tar" } case ".bz2", ".bzip2", ".tbz2": c.Put(ContentEncoding, "bzip2") if ext == "tbz2" { ext = "tar" } } return mime.TypeByExtension(ext), nil }
func commands(cxt cookoo.Context, router *cookoo.Router) []cli.Command { return []cli.Command{ { Name: "create", ShortName: "init", Usage: "Initialize a new project, creating a template glide.yaml", Description: `This command starts from a project without Glide and sets it up. Once this step is done, you may edit the glide.yaml file and then you may run 'glide install' to fetch your initial dependencies. By default, the project name is 'main'. You can specify an alternative on the commandline: $ glide create github.com/Masterminds/foo For a project that already has a glide.yaml file, you may skip 'glide create' and instead run 'glide up'.`, Action: func(c *cli.Context) { if len(c.Args()) >= 1 { cxt.Put("project", c.Args()[0]) } setupHandler(c, "create", cxt, router) }, }, { Name: "get", Usage: "Install one or more package into `vendor/` and add depdency to glide.yaml.", Description: `Gets one or more package (like 'go get') and then adds that file to the glide.yaml file. Multiple package names can be specified on one line. $ glide get github.com/Masterminds/cookoo/web The above will install the project github.com/Masterminds/cookoo and add the subpackage 'web'. If a fetched dependency has a glide.yaml file, 'get' will also install all of the dependencies for that dependency. Those are installed in a scoped vendor directory. So dependency vendor/foo/bar has its dependencies stored in vendor/foo/bar/vendor. This behavior can be disabled using '--no-recursive' If '--import' is set, this will also read the dependency projects, looking for gb, Godep and GPM files. When it finds them, it will build a comparable glide.yaml file, and then fetch all of the necessary dependencies. The dependencies are then vendored in the appropriate project. Subsequent calls to 'glide up' will use the glide.yaml to maintain those dependencies. However, only if you call 'glide up --import' will the glide file be rebuilt. When '--no-recursive' is used, '--import' does nothing. `, Flags: []cli.Flag{ cli.BoolFlag{ Name: "no-recursive", Usage: "Disable updating dependencies' dependencies.", }, cli.BoolFlag{ Name: "import", Usage: "When fetching dependencies, convert Godeps (GPM, Godep) to glide.yaml and pull dependencies", }, }, Action: func(c *cli.Context) { if len(c.Args()) < 1 { fmt.Println("Oops! Package name is required.") os.Exit(1) } cxt.Put("packages", []string(c.Args())) cxt.Put("recursiveDependencies", !c.Bool("no-recursive")) if c.Bool("import") { cxt.Put("importGodeps", true) cxt.Put("importGPM", true) cxt.Put("importGb", true) } setupHandler(c, "get", cxt, router) }, }, { Name: "import", Usage: "Import files from other dependency management systems.", Subcommands: []cli.Command{ { Name: "godep", Usage: "Import Godep's Godeps.json files and display the would-be yaml file", Action: func(c *cli.Context) { setupHandler(c, "import godep", cxt, router) }, }, { Name: "gpm", Usage: "Import GPM's Godeps and Godeps-Git files and display the would-be yaml file", Action: func(c *cli.Context) { setupHandler(c, "import gpm", cxt, router) }, }, { Name: "gb", Usage: "Import gb's manifest file and display the would-be yaml file", Action: func(c *cli.Context) { setupHandler(c, "import gb", cxt, router) }, }, }, }, { Name: "name", Usage: "Print the name of this project.", Description: `Read the glide.yaml file and print the name given on the 'package' line.`, Action: func(c *cli.Context) { setupHandler(c, "name", cxt, router) }, }, { Name: "novendor", ShortName: "nv", Usage: "List all non-vendor paths in a directory.", Description: `Given a directory, list all the relevant Go paths that are not vendored. Example: $ go test $(glide novendor) `, Action: func(c *cli.Context) { setupHandler(c, "nv", cxt, router) }, }, { Name: "pin", Usage: "Print a YAML file with all of the packages pinned to the current version", Description: `Begins with the current glide.yaml and sets an absolute ref for every package. The version is derived from the repository version. It will be either a commit or a tag, depending on the state of the VCS tree. By default, output is written to standard out. However, if you supply a filename, the data will be written to that: $ glide pin glide.yaml The above will overwrite your glide.yaml file. You have been warned. `, Action: func(c *cli.Context) { outfile := "" if len(c.Args()) == 1 { outfile = c.Args()[0] } cxt.Put("toPath", outfile) setupHandler(c, "pin", cxt, router) }, }, { Name: "rebuild", Usage: "Rebuild ('go build') the dependencies", Description: `This rebuilds the packages' '.a' files. On some systems this can improve performance on subsequent 'go run' and 'go build' calls.`, Action: func(c *cli.Context) { setupHandler(c, "rebuild", cxt, router) }, }, { Name: "update", ShortName: "up", Aliases: []string{"install"}, Usage: "Update or install a project's dependencies", Description: `This uses the native VCS of each package to try to pull the most applicable updates. Packages with fixed refs (Versions or tags) will not be updated. Packages with no ref or with a branch ref will be updated as expected. If a dependency has a glide.yaml file, update will read that file and update those dependencies accordingly. Those dependencies are maintained in a scoped vendor directory. 'vendor/foo/bar' will have its dependencies stored in 'vendor/foo/bar/vendor'. This behavior can be disabled with '--no-recursive'. If the '--import' flag is specified, Glide will also import Godep and GPM files as it finds them in dependencies. It will create a glide.yaml file from the Godeps data, and then update. This has no effect if '--no-recursive' is set. If the '--update-vendored' flag (aliased to '-u') is present vendored dependencies, stored in your projects VCS repository, will be updated. This works by removing the old package, checking out an the repo and setting the version, and removing the VCS directory. If the '--delete-flatten' flag is present, Glide will remove any dependencies marked flatten within dependencies. `, Flags: []cli.Flag{ cli.BoolFlag{ Name: "delete", Usage: "Delete vendor packages not specified in config.", }, cli.BoolFlag{ Name: "no-recursive", Usage: "Disable updating dependencies' dependencies.", }, cli.BoolFlag{ Name: "import", Usage: "When updating dependencies, convert Godeps (GPM, Godep) to glide.yaml and pull dependencies", }, cli.BoolFlag{ Name: "force", Usage: "If there was a change in the repo or VCS switch to new one. Warning, changes will be lost.", }, cli.BoolFlag{ Name: "update-vendored, u", Usage: "Update vendored packages (without local VCS repo). Warning, changes will be lost.", }, cli.BoolFlag{ Name: "delete-flatten", Usage: "Delete flattened vendor packages.", }, }, Action: func(c *cli.Context) { cxt.Put("deleteOptIn", c.Bool("delete")) cxt.Put("forceUpdate", c.Bool("force")) cxt.Put("recursiveDependencies", !c.Bool("no-recursive")) cxt.Put("deleteFlatten", c.Bool("delete-flatten")) if c.Bool("import") { cxt.Put("importGodeps", true) cxt.Put("importGPM", true) cxt.Put("importGb", true) } cxt.Put("updateVendoredDeps", c.Bool("update-vendored")) cxt.Put("packages", []string(c.Args())) setupHandler(c, "update", cxt, router) }, }, { Name: "tree", Usage: "Tree prints the dependencies of this project as a tree.", Description: `This scans a project's source files and builds a tree representation of the import graph. It ignores testdata/ and directories that begin with . or _. Packages in vendor/ are only included if they are referenced by the main project or one of its dependencies.`, Action: func(c *cli.Context) { setupHandler(c, "tree", cxt, router) }, }, { Name: "list", Usage: "List prints all dependencies that Glide could discover.", Description: `List scans your code and lists all of the packages that are used. It does not use the glide.yaml. Instead, it inspects the code to determine what packages are imported. Directories that begin with . or _ are ignored, as are testdata directories. Packages in vendor are only included if they are used by the project. `, Action: func(c *cli.Context) { setupHandler(c, "list", cxt, router) }, }, { Name: "guess", Usage: "Guess dependencies for existing source.", Description: `This looks through existing source and dependencies, and tries to guess all of the dependent packages. By default, 'glide guess' writes to standard output. But if a filename is supplied, the results are written to the file: $ glide guess glide.yaml The above will overwrite the glide.yaml file.`, Action: func(c *cli.Context) { outfile := "" if len(c.Args()) == 1 { outfile = c.Args()[0] } cxt.Put("toPath", outfile) setupHandler(c, "guess", cxt, router) }, }, { Name: "about", Usage: "Learn about Glide", Action: func(c *cli.Context) { setupHandler(c, "about", cxt, router) }, }, } }
// Flatten recurses through all dependent packages and flattens to a top level. // // Flattening involves determining a tree's dependencies and flattening them // into a single large list. // // Params: // - packages ([]string): The packages to read. If this is empty, it reads all // packages. // - force (bool): force vcs updates. // - conf (*cfg.Config): The configuration. // // Returns: // func Flatten(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { conf := p.Get("conf", &cfg.Config{}).(*cfg.Config) skip := p.Get("skip", false).(bool) home := p.Get("home", "").(string) cache := p.Get("cache", false).(bool) cacheGopath := p.Get("cacheGopath", false).(bool) useGopath := p.Get("useGopath", false).(bool) if skip { Warn("Skipping lockfile generation because full dependency tree is not being calculated") return conf, nil } packages := p.Get("packages", []string{}).([]string) // Operate on a clone of the conf so any changes don't impact later operations. // This is a deep clone so dependencies are also cloned. confcopy := conf.Clone() // Generate a hash of the conf for later use in lockfile generation. hash, err := conf.Hash() if err != nil { return conf, err } // When packages are passed around with a #version on the end it needs // to be stripped. for k, v := range packages { parts := strings.Split(v, "#") packages[k] = parts[0] } force := p.Get("force", true).(bool) vend, _ := VendorPath(c) // If no packages are supplied, we do them all. if len(packages) == 0 { packages = make([]string, len(confcopy.Imports)) for i, v := range confcopy.Imports { packages[i] = v.Name } } // Build an initial dependency map. deps := make(map[string]*cfg.Dependency, len(confcopy.Imports)) for _, imp := range confcopy.Imports { deps[imp.Name] = imp } f := &flattening{confcopy, vend, vend, deps, packages} // The assumption here is that once something has been scanned once in a // run, there is no need to scan it again. scanned := map[string]bool{} err = recFlatten(f, force, home, cache, cacheGopath, useGopath, scanned) if err != nil { return confcopy, err } err = confcopy.DeDupe() if err != nil { return confcopy, err } flattenSetRefs(f) Info("Project relies on %d dependencies.", len(deps)) c.Put("Lockfile", cfg.LockfileFromMap(deps, hash)) exportFlattenedDeps(confcopy, deps) return confcopy, err }
func commands(cxt cookoo.Context, router *cookoo.Router) []cli.Command { return []cli.Command{ { Name: "create", ShortName: "init", Usage: "Initialize a new project, creating a glide.yaml file", Description: `This command starts from a project without Glide and sets it up. It generates a glide.yaml file, parsing your codebase to guess the dependencies to include. Once this step is done you may edit the glide.yaml file to update imported dependency properties such as the version or version range to include. To fetch the dependencies you may run 'glide install'.`, Flags: []cli.Flag{ cli.BoolFlag{ Name: "skip-import", Usage: "When initializing skip importing from other package managers.", }, }, Action: func(c *cli.Context) { cxt.Put("skipImport", c.Bool("skip-import")) setupHandler(c, "create", cxt, router) }, }, { Name: "get", Usage: "Install one or more packages into `vendor/` and add dependency to glide.yaml.", Description: `Gets one or more package (like 'go get') and then adds that file to the glide.yaml file. Multiple package names can be specified on one line. $ glide get github.com/Masterminds/cookoo/web The above will install the project github.com/Masterminds/cookoo and add the subpackage 'web'. If a fetched dependency has a glide.yaml file, configuration from Godep, GPM, or GB Glide that configuration will be used to find the dependencies and versions to fetch. If those are not available the dependent packages will be fetched as either a version specified elsewhere or the latest version. When adding a new dependency Glide will perform an update to work out the the versions to use from the dependency tree. This will generate an updated glide.lock file with specific locked versions to use. `, Flags: []cli.Flag{ cli.BoolFlag{ Name: "insecure", Usage: "Use http:// rather than https:// to retrieve pacakges.", }, cli.BoolFlag{ Name: "no-recursive, quick", Usage: "Disable updating dependencies' dependencies.", }, cli.BoolFlag{ Name: "force", Usage: "If there was a change in the repo or VCS switch to new one. Warning, changes will be lost.", }, cli.BoolFlag{ Name: "update-vendored, u", Usage: "Update vendored packages (without local VCS repo). Warning, changes will be lost.", }, cli.BoolFlag{ Name: "cache", Usage: "When downloading dependencies attempt to cache them.", }, cli.BoolFlag{ Name: "cache-gopath", Usage: "When downloading dependencies attempt to put them in the GOPATH, too.", }, cli.BoolFlag{ Name: "use-gopath", Usage: "Copy dependencies from the GOPATH if they exist there.", }, }, Action: func(c *cli.Context) { if len(c.Args()) < 1 { fmt.Println("Oops! Package name is required.") os.Exit(1) } cxt.Put("forceUpdate", c.Bool("force")) cxt.Put("packages", []string(c.Args())) cxt.Put("skipFlatten", !c.Bool("no-recursive")) cxt.Put("insecure", c.Bool("insecure")) cxt.Put("useCache", c.Bool("cache")) cxt.Put("cacheGopath", c.Bool("cache-gopath")) cxt.Put("useGopath", c.Bool("use-gopath")) // FIXME: Are these used anywhere? if c.Bool("import") { cxt.Put("importGodeps", true) cxt.Put("importGPM", true) cxt.Put("importGb", true) } cxt.Put("updateVendoredDeps", c.Bool("update-vendored")) setupHandler(c, "get", cxt, router) }, }, { Name: "import", Usage: "Import files from other dependency management systems.", Subcommands: []cli.Command{ { Name: "godep", Usage: "Import Godep's Godeps.json files and display the would-be yaml file", Flags: []cli.Flag{ cli.StringFlag{ Name: "file, f", Usage: "Save all of the discovered dependencies to a Glide YAML file.", }, }, Action: func(c *cli.Context) { cxt.Put("toPath", c.String("file")) setupHandler(c, "import godep", cxt, router) }, }, { Name: "gpm", Usage: "Import GPM's Godeps and Godeps-Git files and display the would-be yaml file", Flags: []cli.Flag{ cli.StringFlag{ Name: "file, f", Usage: "Save all of the discovered dependencies to a Glide YAML file.", }, }, Action: func(c *cli.Context) { cxt.Put("toPath", c.String("file")) setupHandler(c, "import gpm", cxt, router) }, }, { Name: "gb", Usage: "Import gb's manifest file and display the would-be yaml file", Flags: []cli.Flag{ cli.StringFlag{ Name: "file, f", Usage: "Save all of the discovered dependencies to a Glide YAML file.", }, }, Action: func(c *cli.Context) { cxt.Put("toPath", c.String("file")) setupHandler(c, "import gb", cxt, router) }, }, }, }, { Name: "name", Usage: "Print the name of this project.", Description: `Read the glide.yaml file and print the name given on the 'package' line.`, Action: func(c *cli.Context) { setupHandler(c, "name", cxt, router) }, }, { Name: "novendor", ShortName: "nv", Usage: "List all non-vendor paths in a directory.", Description: `Given a directory, list all the relevant Go paths that are not vendored. Example: $ go test $(glide novendor) `, Action: func(c *cli.Context) { setupHandler(c, "nv", cxt, router) }, }, // { // Name: "pin", // Usage: "Print a YAML file with all of the packages pinned to the current version", // Description: `Begins with the current glide.yaml and sets an absolute ref // for every package. The version is derived from the repository version. It will be // either a commit or a tag, depending on the state of the VCS tree. // // By default, output is written to standard out. However, if you supply a filename, // the data will be written to that: // // $ glide pin glide.yaml // // The above will overwrite your glide.yaml file. You have been warned. // `, // Action: func(c *cli.Context) { // outfile := "" // if len(c.Args()) == 1 { // outfile = c.Args()[0] // } // cxt.Put("toPath", outfile) // setupHandler(c, "pin", cxt, router) // }, // }, { Name: "rebuild", Usage: "Rebuild ('go build') the dependencies", Description: `This rebuilds the packages' '.a' files. On some systems this can improve performance on subsequent 'go run' and 'go build' calls.`, Action: func(c *cli.Context) { setupHandler(c, "rebuild", cxt, router) }, }, { Name: "install", ShortName: "i", Usage: "Install a project's dependencies", Description: `This uses the native VCS of each packages to install the appropriate version. There are two ways a projects dependencies can be installed. When there is a glide.yaml file defining the dependencies but no lock file (glide.lock) the dependencies are installed using the "update" command and a glide.lock file is generated pinning all dependencies. If a glide.lock file is already present the dependencies are installed or updated from the lock file.`, Flags: []cli.Flag{ cli.BoolFlag{ Name: "delete", Usage: "Delete vendor packages not specified in config.", }, cli.BoolFlag{ Name: "no-recursive, quick", Usage: "Disable updating dependencies' dependencies. Only update things in glide.yaml.", }, cli.BoolFlag{ Name: "force", Usage: "If there was a change in the repo or VCS switch to new one. Warning, changes will be lost.", }, cli.BoolFlag{ Name: "update-vendored, u", Usage: "Update vendored packages (without local VCS repo). Warning, changes will be lost.", }, cli.StringFlag{ Name: "file, f", Usage: "Save all of the discovered dependencies to a Glide YAML file.", }, cli.BoolFlag{ Name: "cache", Usage: "When downloading dependencies attempt to cache them.", }, cli.BoolFlag{ Name: "cache-gopath", Usage: "When downloading dependencies attempt to put them in the GOPATH, too.", }, cli.BoolFlag{ Name: "use-gopath", Usage: "Copy dependencies from the GOPATH if they exist there.", }, }, Action: func(c *cli.Context) { cxt.Put("deleteOptIn", c.Bool("delete")) cxt.Put("forceUpdate", c.Bool("force")) cxt.Put("skipFlatten", c.Bool("no-recursive")) cxt.Put("deleteFlatten", c.Bool("delete-flatten")) cxt.Put("toPath", c.String("file")) cxt.Put("toStdout", false) cxt.Put("useCache", c.Bool("cache")) cxt.Put("cacheGopath", c.Bool("cache-gopath")) cxt.Put("useGopath", c.Bool("use-gopath")) if c.Bool("import") { cxt.Put("importGodeps", true) cxt.Put("importGPM", true) cxt.Put("importGb", true) } cxt.Put("updateVendoredDeps", c.Bool("update-vendored")) cxt.Put("packages", []string(c.Args())) setupHandler(c, "install", cxt, router) }, }, { Name: "update", ShortName: "up", Usage: "Update a project's dependencies", Description: `This uses the native VCS of each package to try to pull the most applicable updates. Packages with fixed refs (Versions or tags) will not be updated. Packages with no ref or with a branch ref will be updated as expected. If a dependency has a glide.yaml file, update will read that file and update those dependencies accordingly. Those dependencies are maintained in a the top level 'vendor/' directory. 'vendor/foo/bar' will have its dependencies stored in 'vendor/'. This behavior can be disabled with '--no-recursive'. When this behavior is skipped a glide.lock file is not generated because the full dependency tree cannot be known. Glide will also import Godep, GB, and GPM files as it finds them in dependencies. It will create a glide.yaml file from the Godeps data, and then update. This has no effect if '--no-recursive' is set. If the '--update-vendored' flag (aliased to '-u') is present vendored dependencies, stored in your projects VCS repository, will be updated. This works by removing the old package, checking out an the repo and setting the version, and removing the VCS directory. By default, packages that are discovered are considered transient, and are not stored in the glide.yaml file. The --file=NAME.yaml flag allows you to save the discovered dependencies to a YAML file. `, Flags: []cli.Flag{ cli.BoolFlag{ Name: "delete", Usage: "Delete vendor packages not specified in config.", }, cli.BoolFlag{ Name: "no-recursive, quick", Usage: "Disable updating dependencies' dependencies. Only update things in glide.yaml.", }, cli.BoolFlag{ Name: "force", Usage: "If there was a change in the repo or VCS switch to new one. Warning, changes will be lost.", }, cli.BoolFlag{ Name: "update-vendored, u", Usage: "Update vendored packages (without local VCS repo). Warning, changes will be lost.", }, cli.StringFlag{ Name: "file, f", Usage: "Save all of the discovered dependencies to a Glide YAML file.", }, cli.BoolFlag{ Name: "cache", Usage: "When downloading dependencies attempt to cache them.", }, cli.BoolFlag{ Name: "cache-gopath", Usage: "When downloading dependencies attempt to put them in the GOPATH, too.", }, cli.BoolFlag{ Name: "use-gopath", Usage: "Copy dependencies from the GOPATH if they exist there.", }, }, Action: func(c *cli.Context) { cxt.Put("deleteOptIn", c.Bool("delete")) cxt.Put("forceUpdate", c.Bool("force")) cxt.Put("skipFlatten", c.Bool("no-recursive")) cxt.Put("deleteFlatten", c.Bool("delete-flatten")) cxt.Put("toPath", c.String("file")) cxt.Put("toStdout", false) cxt.Put("useCache", c.Bool("cache")) cxt.Put("cacheGopath", c.Bool("cache-gopath")) cxt.Put("useGopath", c.Bool("use-gopath")) if c.Bool("import") { cxt.Put("importGodeps", true) cxt.Put("importGPM", true) cxt.Put("importGb", true) } cxt.Put("updateVendoredDeps", c.Bool("update-vendored")) cxt.Put("packages", []string(c.Args())) setupHandler(c, "update", cxt, router) }, }, { Name: "tree", Usage: "Tree prints the dependencies of this project as a tree.", Description: `This scans a project's source files and builds a tree representation of the import graph. It ignores testdata/ and directories that begin with . or _. Packages in vendor/ are only included if they are referenced by the main project or one of its dependencies.`, Action: func(c *cli.Context) { setupHandler(c, "tree", cxt, router) }, }, { Name: "list", Usage: "List prints all dependencies that Glide could discover.", Description: `List scans your code and lists all of the packages that are used. It does not use the glide.yaml. Instead, it inspects the code to determine what packages are imported. Directories that begin with . or _ are ignored, as are testdata directories. Packages in vendor are only included if they are used by the project. `, Action: func(c *cli.Context) { setupHandler(c, "list", cxt, router) }, }, { Name: "about", Usage: "Learn about Glide", Action: func(c *cli.Context) { setupHandler(c, "about", cxt, router) }, }, } }