{"action":"created","comment":{"url":"https://api.github.com/repos/hashicorp/nomad/pulls/comments/236514751","pull_request_review_id":178604403,"id":236514751,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIzNjUxNDc1MQ==","diff_hunk":"@@ -0,0 +1,510 @@\n+//+build linux,lxc\n+\n+package lxc\n+\n+import (\n+\t\"context\"\n+\t\"fmt\"\n+\t\"strconv\"\n+\t\"time\"\n+\n+\thclog \"github.com/hashicorp/go-hclog\"\n+\t\"github.com/hashicorp/nomad/client/stats\"\n+\tcstructs \"github.com/hashicorp/nomad/client/structs\"\n+\t\"github.com/hashicorp/nomad/drivers/shared/eventer\"\n+\t\"github.com/hashicorp/nomad/plugins/base\"\n+\t\"github.com/hashicorp/nomad/plugins/drivers\"\n+\t\"github.com/hashicorp/nomad/plugins/shared/hclspec\"\n+\t\"github.com/hashicorp/nomad/plugins/shared/loader\"\n+\n+\tlxc \"gopkg.in/lxc/go-lxc.v2\"\n+)\n+\n+const (\n+\t// pluginName is the name of the plugin\n+\tpluginName = \"lxc\"\n+\n+\t// fingerprintPeriod is the interval at which the driver will send fingerprint responses\n+\tfingerprintPeriod = 30 * time.Second\n+)\n+\n+var (\n+\t// PluginID is the rawexec plugin metadata registered in the plugin\n+\t// catalog.\n+\tPluginID = loader.PluginID{\n+\t\tName:       pluginName,\n+\t\tPluginType: base.PluginTypeDriver,\n+\t}\n+\n+\t// PluginConfig is the rawexec factory function registered in the\n+\t// plugin catalog.\n+\tPluginConfig = &loader.InternalPluginConfig{\n+\t\tConfig:  map[string]interface{}{},\n+\t\tFactory: func(l hclog.Logger) interface{} { return NewLXCDriver(l) },\n+\t}\n+)\n+\n+// PluginLoader maps pre-0.9 client driver options to post-0.9 plugin options.\n+func PluginLoader(opts map[string]string) (map[string]interface{}, error) {\n+\tconf := map[string]interface{}{}\n+\tif v, err := strconv.ParseBool(opts[\"driver.lxc.enable\"]); err == nil {\n+\t\tconf[\"enabled\"] = v\n+\t}\n+\tif v, err := strconv.ParseBool(opts[\"lxc.volumes.enabled\"]); err == nil {\n+\t\tconf[\"volumes\"] = v\n+\t}\n+\tif v, ok := opts[\"driver.lxc.path\"]; ok {\n+\t\tconf[\"path\"] = v\n+\t}\n+\treturn conf, nil\n+}\n+\n+var (\n+\t// pluginInfo is the response returned for the PluginInfo RPC\n+\tpluginInfo = &base.PluginInfoResponse{\n+\t\tType:             base.PluginTypeDriver,\n+\t\tPluginApiVersion: \"0.0.1\",\n+\t\tPluginVersion:    \"0.1.0\",\n+\t\tName:             pluginName,\n+\t}\n+\n+\t// configSpec is the hcl specification returned by the ConfigSchema RPC\n+\tconfigSpec = hclspec.NewObject(map[string]*hclspec.Spec{\n+\t\t\"enabled\": hclspec.NewDefault(\n+\t\t\thclspec.NewAttr(\"enabled\", \"bool\", false),\n+\t\t\thclspec.NewLiteral(\"true\"),\n+\t\t),\n+\t\t\"volumes\": hclspec.NewDefault(\n+\t\t\thclspec.NewAttr(\"volumes\", \"bool\", false),\n+\t\t\thclspec.NewLiteral(\"true\"),\n+\t\t),\n+\t\t\"path\": hclspec.NewDefault(\n+\t\t\thclspec.NewAttr(\"path\", \"string\", false),\n+\t\t\thclspec.NewLiteral(\"\\\"\\\"\"),\n+\t\t),\n+\t})\n+\n+\t// taskConfigSpec is the hcl specification for the driver config section of\n+\t// a task within a job. It is returned in the TaskConfigSchema RPC\n+\ttaskConfigSpec = hclspec.NewObject(map[string]*hclspec.Spec{\n+\t\t\"template\":       hclspec.NewAttr(\"template\", \"string\", true),\n+\t\t\"distro\":         hclspec.NewAttr(\"distro\", \"string\", false),\n+\t\t\"release\":        hclspec.NewAttr(\"release\", \"string\", false),\n+\t\t\"arch\":           hclspec.NewAttr(\"arch\", \"string\", false),\n+\t\t\"image_variant\":  hclspec.NewAttr(\"image_variant\", \"string\", false),\n+\t\t\"image_server\":   hclspec.NewAttr(\"image_server\", \"string\", false),\n+\t\t\"gpg_key_id\":     hclspec.NewAttr(\"gpg_key_id\", \"string\", false),\n+\t\t\"gpg_key_server\": hclspec.NewAttr(\"gpg_key_server\", \"string\", false),\n+\t\t\"disable_gpg\":    hclspec.NewAttr(\"disable_gpg\", \"string\", false),\n+\t\t\"flush_cache\":    hclspec.NewAttr(\"flush_cache\", \"string\", false),\n+\t\t\"force_cache\":    hclspec.NewAttr(\"force_cache\", \"string\", false),\n+\t\t\"template_args\":  hclspec.NewAttr(\"template_args\", \"list(string)\", false),\n+\t\t\"log_level\":      hclspec.NewAttr(\"log_level\", \"string\", false),\n+\t\t\"verbosity\":      hclspec.NewAttr(\"verbosity\", \"string\", false),\n+\t\t\"volumes\":        hclspec.NewAttr(\"volumes\", \"list(string)\", false),\n+\t})\n+\n+\t// capabilities is returned by the Capabilities RPC and indicates what\n+\t// optional features this driver supports\n+\tcapabilities = &drivers.Capabilities{\n+\t\tSendSignals: false,\n+\t\tExec:        false,\n+\t\tFSIsolation: cstructs.FSIsolationImage,\n+\t}\n+)\n+\n+// Driver is a privileged version of the exec driver. It provides no\n+// resource isolation and just fork/execs. The Exec driver should be preferred\n+// and this should only be used when explicitly needed.\n+type Driver struct {\n+\t// eventer is used to handle multiplexing of TaskEvents calls such that an\n+\t// event can be broadcast to all callers\n+\teventer *eventer.Eventer\n+\n+\t// config is the driver configuration set by the SetConfig RPC\n+\tconfig *Config\n+\n+\t// nomadConfig is the client config from nomad\n+\tnomadConfig *base.ClientDriverConfig\n+\n+\t// tasks is the in memory datastore mapping taskIDs to rawExecDriverHandles\n+\ttasks *taskStore\n+\n+\t// ctx is the context for the driver. It is passed to other subsystems to\n+\t// coordinate shutdown\n+\tctx context.Context\n+\n+\t// signalShutdown is called when the driver is shutting down and cancels the\n+\t// ctx passed to any subsystems\n+\tsignalShutdown context.CancelFunc\n+\n+\t// logger will log to the Nomad agent\n+\tlogger hclog.Logger\n+}\n+\n+// Config is the driver configuration set by the SetConfig RPC call\n+type Config struct {\n+\t// Enabled is set to true to enable the raw_exec driver\n+\tEnabled bool `codec:\"enabled\"`\n+\n+\tAllowVolumes bool `codec:\"volumes\"`\n+\n+\tPath string `codec:\"path\"`\n+}\n+\n+// TaskConfig is the driver configuration of a task within a job\n+type TaskConfig struct {\n+\tTemplate             string   `codec:\"template\"`\n+\tDistro               string   `codec:\"distro\"`\n+\tRelease              string   `codec:\"release\"`\n+\tArch                 string   `codec:\"arch\"`\n+\tImageVariant         string   `codec:\"image_variant\"`\n+\tImageServer          string   `codec:\"image_server\"`\n+\tGPGKeyID             string   `codec:\"gpg_key_id\"`\n+\tGPGKeyServer         string   `codec:\"gpg_key_server\"`\n+\tDisableGPGValidation bool     `codec:\"disable_gpg\"`\n+\tFlushCache           bool     `codec:\"flush_cache\"`\n+\tForceCache           bool     `codec:\"force_cache\"`\n+\tTemplateArgs         []string `codec:\"template_args\"`\n+\tLogLevel             string   `codec:\"log_level\"`\n+\tVerbosity            string   `codec:\"verbosity\"`\n+\tVolumes              []string `codec:\"volumes\"`\n+}\n+\n+// TaskState is the state which is encoded in the handle returned in\n+// StartTask. This information is needed to rebuild the task state and handler\n+// during recovery.\n+type TaskState struct {\n+\tTaskConfig    *drivers.TaskConfig\n+\tContainerName string\n+\tStartedAt     time.Time\n+}\n+\n+// NewLXCDriver returns a new DriverPlugin implementation\n+func NewLXCDriver(logger hclog.Logger) drivers.DriverPlugin {\n+\tctx, cancel := context.WithCancel(context.Background())\n+\tlogger = logger.Named(pluginName)\n+\treturn &Driver{\n+\t\teventer:        eventer.NewEventer(ctx, logger),\n+\t\tconfig:         &Config{},\n+\t\ttasks:          newTaskStore(),\n+\t\tctx:            ctx,\n+\t\tsignalShutdown: cancel,\n+\t\tlogger:         logger,\n+\t}\n+}\n+\n+func (d *Driver) PluginInfo() (*base.PluginInfoResponse, error) {\n+\treturn pluginInfo, nil\n+}\n+\n+func (d *Driver) ConfigSchema() (*hclspec.Spec, error) {\n+\treturn configSpec, nil\n+}\n+\n+func (d *Driver) SetConfig(data []byte, cfg *base.ClientAgentConfig) error {\n+\tvar config Config\n+\tif err := base.MsgPackDecode(data, &config); err != nil {\n+\t\treturn err\n+\t}\n+\n+\td.config = &config\n+\tif cfg != nil {\n+\t\td.nomadConfig = cfg.Driver\n+\t}\n+\n+\treturn nil\n+}\n+\n+func (d *Driver) Shutdown(ctx context.Context) error {\n+\td.signalShutdown()\n+\treturn nil\n+}\n+\n+func (d *Driver) TaskConfigSchema() (*hclspec.Spec, error) {\n+\treturn taskConfigSpec, nil\n+}\n+\n+func (d *Driver) Capabilities() (*drivers.Capabilities, error) {\n+\treturn capabilities, nil\n+}\n+\n+func (d *Driver) Fingerprint(ctx context.Context) (<-chan *drivers.Fingerprint, error) {\n+\tch := make(chan *drivers.Fingerprint)\n+\tgo d.handleFingerprint(ctx, ch)\n+\treturn ch, nil\n+}\n+\n+func (d *Driver) handleFingerprint(ctx context.Context, ch chan<- *drivers.Fingerprint) {\n+\tdefer close(ch)\n+\tticker := time.NewTimer(0)\n+\tfor {\n+\t\tselect {\n+\t\tcase <-ctx.Done():\n+\t\t\treturn\n+\t\tcase <-d.ctx.Done():\n+\t\t\treturn\n+\t\tcase <-ticker.C:\n+\t\t\tticker.Reset(fingerprintPeriod)\n+\t\t\tch <- d.buildFingerprint()\n+\t\t}\n+\t}\n+}\n+\n+func (d *Driver) buildFingerprint() *drivers.Fingerprint {\n+\tvar health drivers.HealthState\n+\tvar desc string\n+\tattrs := map[string]string{}\n+\n+\tlxcVersion := lxc.Version()\n+\n+\tif d.config.Enabled && lxcVersion != \"\" {\n+\t\thealth = drivers.HealthStateHealthy\n+\t\tdesc = \"ready\"\n+\t\tattrs[\"driver.lxc\"] = \"1\"\n+\t\tattrs[\"driver.lxc.version\"] = lxcVersion\n+\t} else {\n+\t\thealth = drivers.HealthStateUndetected\n+\t\tdesc = \"disabled\"\n+\t}\n+\n+\tif d.config.AllowVolumes {\n+\t\tattrs[\"driver.lxc.volumes.enabled\"] = \"1\"\n+\t}\n+\n+\treturn &drivers.Fingerprint{\n+\t\tAttributes:        attrs,\n+\t\tHealth:            health,\n+\t\tHealthDescription: desc,\n+\t}\n+}\n+\n+func (d *Driver) RecoverTask(handle *drivers.TaskHandle) error {\n+\tif handle == nil {\n+\t\treturn fmt.Errorf(\"error: handle cannot be nil\")\n+\t}\n+\n+\tif _, ok := d.tasks.Get(handle.Config.ID); ok {\n+\t\treturn nil\n+\t}\n+\n+\tvar taskState TaskState\n+\tif err := handle.GetDriverState(&taskState); err != nil {\n+\t\treturn fmt.Errorf(\"failed to decode task state from handle: %v\", err)\n+\t}\n+\n+\tvar driverConfig TaskConfig\n+\tif err := taskState.TaskConfig.DecodeDriverConfig(&driverConfig); err != nil {\n+\t\treturn fmt.Errorf(\"failed to decode driver config: %v\", err)\n+\t}\n+\n+\tc, err := lxc.NewContainer(taskState.ContainerName, d.lxcPath())\n+\tif err != nil {\n+\t\treturn fmt.Errorf(\"failed to create container ref: %v\", err)\n+\t}\n+\n+\tinitPid := c.InitPid()\n+\th := &taskHandle{\n+\t\tcontainer:  c,\n+\t\tinitPid:    initPid,\n+\t\ttaskConfig: taskState.TaskConfig,\n+\t\tprocState:  drivers.TaskStateRunning,\n+\t\tstartedAt:  taskState.StartedAt,\n+\t\texitResult: &drivers.ExitResult{},\n+\n+\t\ttotalCpuStats:  stats.NewCpuStats(),\n+\t\tuserCpuStats:   stats.NewCpuStats(),\n+\t\tsystemCpuStats: stats.NewCpuStats(),\n+\t}\n+\n+\td.tasks.Set(taskState.TaskConfig.ID, h)\n+\n+\tgo h.run()\n+\treturn nil\n+}\n+\n+func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *cstructs.DriverNetwork, error) {\n+\tif _, ok := d.tasks.Get(cfg.ID); ok {\n+\t\treturn nil, nil, fmt.Errorf(\"task with ID %q already started\", cfg.ID)\n+\t}\n+\n+\tvar driverConfig TaskConfig\n+\tif err := cfg.DecodeDriverConfig(&driverConfig); err != nil {\n+\t\treturn nil, nil, fmt.Errorf(\"failed to decode driver config: %v\", err)\n+\t}\n+\n+\td.logger.Info(\"starting lxc task\", \"driver_cfg\", hclog.Fmt(\"%+v\", driverConfig))\n+\thandle := drivers.NewTaskHandle(pluginName)\n+\thandle.Config = cfg\n+\n+\tc, err := d.initializeContainer(cfg, driverConfig)\n+\tif err != nil {\n+\t\treturn nil, nil, err\n+\t}\n+\n+\topt := toLXCCreateOptions(driverConfig)\n+\tif err := c.Create(opt); err != nil {\n+\t\treturn nil, nil, fmt.Errorf(\"unable to create container: %v\", err)\n+\t}\n+\n+\tcleanup := func() {\n+\t\tif err := c.Destroy(); err != nil {\n+\t\t\td.logger.Error(\"failed to clean up from an error in Start\", \"error\", err)\n+\t\t}\n+\t}\n+\n+\tif err := d.configureContainerNetwork(c); err != nil {\n+\t\tcleanup()\n+\t\treturn nil, nil, err\n+\t}\n+\n+\tif err := d.mountVolumes(c, cfg, driverConfig); err != nil {\n+\t\tcleanup()\n+\t\treturn nil, nil, err\n+\t}\n+\n+\tif err := c.Start(); err != nil {\n+\t\tcleanup()\n+\t\treturn nil, nil, fmt.Errorf(\"unable to start container: %v\", err)\n+\t}\n+\n+\tif err := d.setResourceLimits(c, cfg); err != nil {\n+\t\tcleanup()\n+\t\treturn nil, nil, err\n+\t}\n+\n+\tpid := c.InitPid()\n+\n+\th := &taskHandle{\n+\t\tcontainer:  c,\n+\t\tinitPid:    pid,\n+\t\ttaskConfig: cfg,\n+\t\tprocState:  drivers.TaskStateRunning,\n+\t\tstartedAt:  time.Now().Round(time.Millisecond),\n+\t\tlogger:     d.logger,\n+\n+\t\ttotalCpuStats:  stats.NewCpuStats(),\n+\t\tuserCpuStats:   stats.NewCpuStats(),\n+\t\tsystemCpuStats: stats.NewCpuStats(),\n+\t}\n+\n+\tdriverState := TaskState{\n+\t\tContainerName: c.Name(),\n+\t\tTaskConfig:    cfg,\n+\t\tStartedAt:     h.startedAt,\n+\t}\n+\n+\tif err := handle.SetDriverState(&driverState); err != nil {\n+\t\td.logger.Error(\"failed to start task, error setting driver state\", \"error\", err)\n+\t\tcleanup()\n+\t\treturn nil, nil, fmt.Errorf(\"failed to set driver state: %v\", err)\n+\t}\n+\n+\td.tasks.Set(cfg.ID, h)\n+\n+\tgo h.run()\n+\n+\treturn handle, nil, nil\n+}\n+\n+func (d *Driver) WaitTask(ctx context.Context, taskID string) (<-chan *drivers.ExitResult, error) {\n+\thandle, ok := d.tasks.Get(taskID)\n+\tif !ok {\n+\t\treturn nil, drivers.ErrTaskNotFound\n+\t}\n+\n+\tch := make(chan *drivers.ExitResult)\n+\tgo d.handleWait(ctx, handle, ch)\n+\n+\treturn ch, nil\n+}\n+\n+func (d *Driver) handleWait(ctx context.Context, handle *taskHandle, ch chan *drivers.ExitResult) {\n+\tdefer close(ch)\n+\n+\tticker := time.NewTicker(2 * time.Second)\n+\tdefer ticker.Stop()\n+\n+\tfor {\n+\t\tselect {\n+\t\tcase <-ctx.Done():\n+\t\t\treturn\n+\t\tcase <-d.ctx.Done():\n+\t\t\treturn\n+\t\tcase <-ticker.C:","path":"drivers/lxc/driver.go","position":434,"original_position":434,"commit_id":"8544487eb66fbf9636eec2e3a1284731cf6bdb88","original_commit_id":"8544487eb66fbf9636eec2e3a1284731cf6bdb88","user":{"login":"schmichael","id":113362,"node_id":"MDQ6VXNlcjExMzM2Mg==","avatar_url":"https://avatars1.githubusercontent.com/u/113362?v=4","gravatar_id":"","url":"https://api.github.com/users/schmichael","html_url":"https://github.com/schmichael","followers_url":"https://api.github.com/users/schmichael/followers","following_url":"https://api.github.com/users/schmichael/following{/other_user}","gists_url":"https://api.github.com/users/schmichael/gists{/gist_id}","starred_url":"https://api.github.com/users/schmichael/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/schmichael/subscriptions","organizations_url":"https://api.github.com/users/schmichael/orgs","repos_url":"https://api.github.com/users/schmichael/repos","events_url":"https://api.github.com/users/schmichael/events{/privacy}","received_events_url":"https://api.github.com/users/schmichael/received_events","type":"User","site_admin":false},"body":"Whoa! Great find!","created_at":"2018-11-27T04:13:12Z","updated_at":"2018-11-27T04:13:12Z","html_url":"https://github.com/hashicorp/nomad/pull/4918#discussion_r236514751","pull_request_url":"https://api.github.com/repos/hashicorp/nomad/pulls/4918","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/hashicorp/nomad/pulls/comments/236514751"},"html":{"href":"https://github.com/hashicorp/nomad/pull/4918#discussion_r236514751"},"pull_request":{"href":"https://api.github.com/repos/hashicorp/nomad/pulls/4918"}},"in_reply_to_id":236476505},"pull_request":{"url":"https://api.github.com/repos/hashicorp/nomad/pulls/4918","id":233397162,"node_id":"MDExOlB1bGxSZXF1ZXN0MjMzMzk3MTYy","html_url":"https://github.com/hashicorp/nomad/pull/4918","diff_url":"https://github.com/hashicorp/nomad/pull/4918.diff","patch_url":"https://github.com/hashicorp/nomad/pull/4918.patch","issue_url":"https://api.github.com/repos/hashicorp/nomad/issues/4918","number":4918,"state":"open","locked":false,"title":"Add LXC driver","user":{"login":"notnoop","id":136429,"node_id":"MDQ6VXNlcjEzNjQyOQ==","avatar_url":"https://avatars2.githubusercontent.com/u/136429?v=4","gravatar_id":"","url":"https://api.github.com/users/notnoop","html_url":"https://github.com/notnoop","followers_url":"https://api.github.com/users/notnoop/followers","following_url":"https://api.github.com/users/notnoop/following{/other_user}","gists_url":"https://api.github.com/users/notnoop/gists{/gist_id}","starred_url":"https://api.github.com/users/notnoop/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/notnoop/subscriptions","organizations_url":"https://api.github.com/users/notnoop/orgs","repos_url":"https://api.github.com/users/notnoop/repos","events_url":"https://api.github.com/users/notnoop/events{/privacy}","received_events_url":"https://api.github.com/users/notnoop/received_events","type":"User","site_admin":false},"body":"Ports the LXC driver to new plugin system.\r\n\r\nFew incidental changes:\r\n* Updated the go-lxc package to support LXC 3.0 (that was released in [March 2018](https://discuss.linuxcontainers.org/t/lxc-3-0-0-has-been-released/1449)\r\n* There is bug in LXC verification (also present in 0.8.6) where an lxc task without `volumes` field causes a panic\r\n  * Interestingly, old drivers `Validate()` are being used for validation - we need to follow up to move validation to new drivers paths.\r\n\r\nThis PR aims for compatibility with current lxc, including its current limitations.  During testing, I noticed that 0.8.6 has the following limitations:\r\n* LXC driver doesn't capture container exit code: a container that gets killed unexpectedly will still be reporting 0 exit code and does not necessarily get restarted appropriately (specially for batch jobs)\r\n* LXC driver does not capture container logs, and does not support exec or signaling (though LXC provides that capability)\r\n* LXC driver does not destroy the containers, but rather leaves them in a stopped state, potentially using up some disk space.","created_at":"2018-11-25T20:08:17Z","updated_at":"2018-11-27T04:13:12Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f3f8ded9b6826065e6e9af51f8dad5d303449389","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/hashicorp/nomad/pulls/4918/commits","review_comments_url":"https://api.github.com/repos/hashicorp/nomad/pulls/4918/comments","review_comment_url":"https://api.github.com/repos/hashicorp/nomad/pulls/comments{/number}","comments_url":"https://api.github.com/repos/hashicorp/nomad/issues/4918/comments","statuses_url":"https://api.github.com/repos/hashicorp/nomad/statuses/8544487eb66fbf9636eec2e3a1284731cf6bdb88","head":{"label":"hashicorp:f-lxc-driver","ref":"f-lxc-driver","sha":"8544487eb66fbf9636eec2e3a1284731cf6bdb88","user":{"login":"hashicorp","id":761456,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc2MTQ1Ng==","avatar_url":"https://avatars2.githubusercontent.com/u/761456?v=4","gravatar_id":"","url":"https://api.github.com/users/hashicorp","html_url":"https://github.com/hashicorp","followers_url":"https://api.github.com/users/hashicorp/followers","following_url":"https://api.github.com/users/hashicorp/following{/other_user}","gists_url":"https://api.github.com/users/hashicorp/gists{/gist_id}","starred_url":"https://api.github.com/users/hashicorp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hashicorp/subscriptions","organizations_url":"https://api.github.com/users/hashicorp/orgs","repos_url":"https://api.github.com/users/hashicorp/repos","events_url":"https://api.github.com/users/hashicorp/events{/privacy}","received_events_url":"https://api.github.com/users/hashicorp/received_events","type":"Organization","site_admin":false},"repo":{"id":36653430,"node_id":"MDEwOlJlcG9zaXRvcnkzNjY1MzQzMA==","name":"nomad","full_name":"hashicorp/nomad","private":false,"owner":{"login":"hashicorp","id":761456,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc2MTQ1Ng==","avatar_url":"https://avatars2.githubusercontent.com/u/761456?v=4","gravatar_id":"","url":"https://api.github.com/users/hashicorp","html_url":"https://github.com/hashicorp","followers_url":"https://api.github.com/users/hashicorp/followers","following_url":"https://api.github.com/users/hashicorp/following{/other_user}","gists_url":"https://api.github.com/users/hashicorp/gists{/gist_id}","starred_url":"https://api.github.com/users/hashicorp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hashicorp/subscriptions","organizations_url":"https://api.github.com/users/hashicorp/orgs","repos_url":"https://api.github.com/users/hashicorp/repos","events_url":"https://api.github.com/users/hashicorp/events{/privacy}","received_events_url":"https://api.github.com/users/hashicorp/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/hashicorp/nomad","description":"Nomad is a flexible, enterprise-grade cluster scheduler designed to easily integrate into existing workflows. Nomad can run a diverse workload of micro-service, batch, containerized and non-containerized applications. Nomad is easy to operate and scale and integrates seamlessly with Consul and Vault.","fork":false,"url":"https://api.github.com/repos/hashicorp/nomad","forks_url":"https://api.github.com/repos/hashicorp/nomad/forks","keys_url":"https://api.github.com/repos/hashicorp/nomad/keys{/key_id}","collaborators_url":"https://api.github.com/repos/hashicorp/nomad/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/hashicorp/nomad/teams","hooks_url":"https://api.github.com/repos/hashicorp/nomad/hooks","issue_events_url":"https://api.github.com/repos/hashicorp/nomad/issues/events{/number}","events_url":"https://api.github.com/repos/hashicorp/nomad/events","assignees_url":"https://api.github.com/repos/hashicorp/nomad/assignees{/user}","branches_url":"https://api.github.com/repos/hashicorp/nomad/branches{/branch}","tags_url":"https://api.github.com/repos/hashicorp/nomad/tags","blobs_url":"https://api.github.com/repos/hashicorp/nomad/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/hashicorp/nomad/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/hashicorp/nomad/git/refs{/sha}","trees_url":"https://api.github.com/repos/hashicorp/nomad/git/trees{/sha}","statuses_url":"https://api.github.com/repos/hashicorp/nomad/statuses/{sha}","languages_url":"https://api.github.com/repos/hashicorp/nomad/languages","stargazers_url":"https://api.github.com/repos/hashicorp/nomad/stargazers","contributors_url":"https://api.github.com/repos/hashicorp/nomad/contributors","subscribers_url":"https://api.github.com/repos/hashicorp/nomad/subscribers","subscription_url":"https://api.github.com/repos/hashicorp/nomad/subscription","commits_url":"https://api.github.com/repos/hashicorp/nomad/commits{/sha}","git_commits_url":"https://api.github.com/repos/hashicorp/nomad/git/commits{/sha}","comments_url":"https://api.github.com/repos/hashicorp/nomad/comments{/number}","issue_comment_url":"https://api.github.com/repos/hashicorp/nomad/issues/comments{/number}","contents_url":"https://api.github.com/repos/hashicorp/nomad/contents/{+path}","compare_url":"https://api.github.com/repos/hashicorp/nomad/compare/{base}...{head}","merges_url":"https://api.github.com/repos/hashicorp/nomad/merges","archive_url":"https://api.github.com/repos/hashicorp/nomad/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/hashicorp/nomad/downloads","issues_url":"https://api.github.com/repos/hashicorp/nomad/issues{/number}","pulls_url":"https://api.github.com/repos/hashicorp/nomad/pulls{/number}","milestones_url":"https://api.github.com/repos/hashicorp/nomad/milestones{/number}","notifications_url":"https://api.github.com/repos/hashicorp/nomad/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/hashicorp/nomad/labels{/name}","releases_url":"https://api.github.com/repos/hashicorp/nomad/releases{/id}","deployments_url":"https://api.github.com/repos/hashicorp/nomad/deployments","created_at":"2015-06-01T10:21:00Z","updated_at":"2018-11-27T03:43:54Z","pushed_at":"2018-11-27T03:44:49Z","git_url":"git://github.com/hashicorp/nomad.git","ssh_url":"git@github.com:hashicorp/nomad.git","clone_url":"https://github.com/hashicorp/nomad.git","svn_url":"https://github.com/hashicorp/nomad","homepage":"https://www.nomadproject.io/","size":114855,"stargazers_count":4038,"watchers_count":4038,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":791,"mirror_url":null,"archived":false,"open_issues_count":461,"license":{"key":"mpl-2.0","name":"Mozilla Public License 2.0","spdx_id":"MPL-2.0","url":"https://api.github.com/licenses/mpl-2.0","node_id":"MDc6TGljZW5zZTE0"},"forks":791,"open_issues":461,"watchers":4038,"default_branch":"master"}},"base":{"label":"hashicorp:master","ref":"master","sha":"8cab430ac96b631d7a0007da754874c30cd77140","user":{"login":"hashicorp","id":761456,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc2MTQ1Ng==","avatar_url":"https://avatars2.githubusercontent.com/u/761456?v=4","gravatar_id":"","url":"https://api.github.com/users/hashicorp","html_url":"https://github.com/hashicorp","followers_url":"https://api.github.com/users/hashicorp/followers","following_url":"https://api.github.com/users/hashicorp/following{/other_user}","gists_url":"https://api.github.com/users/hashicorp/gists{/gist_id}","starred_url":"https://api.github.com/users/hashicorp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hashicorp/subscriptions","organizations_url":"https://api.github.com/users/hashicorp/orgs","repos_url":"https://api.github.com/users/hashicorp/repos","events_url":"https://api.github.com/users/hashicorp/events{/privacy}","received_events_url":"https://api.github.com/users/hashicorp/received_events","type":"Organization","site_admin":false},"repo":{"id":36653430,"node_id":"MDEwOlJlcG9zaXRvcnkzNjY1MzQzMA==","name":"nomad","full_name":"hashicorp/nomad","private":false,"owner":{"login":"hashicorp","id":761456,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc2MTQ1Ng==","avatar_url":"https://avatars2.githubusercontent.com/u/761456?v=4","gravatar_id":"","url":"https://api.github.com/users/hashicorp","html_url":"https://github.com/hashicorp","followers_url":"https://api.github.com/users/hashicorp/followers","following_url":"https://api.github.com/users/hashicorp/following{/other_user}","gists_url":"https://api.github.com/users/hashicorp/gists{/gist_id}","starred_url":"https://api.github.com/users/hashicorp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hashicorp/subscriptions","organizations_url":"https://api.github.com/users/hashicorp/orgs","repos_url":"https://api.github.com/users/hashicorp/repos","events_url":"https://api.github.com/users/hashicorp/events{/privacy}","received_events_url":"https://api.github.com/users/hashicorp/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/hashicorp/nomad","description":"Nomad is a flexible, enterprise-grade cluster scheduler designed to easily integrate into existing workflows. Nomad can run a diverse workload of micro-service, batch, containerized and non-containerized applications. Nomad is easy to operate and scale and integrates seamlessly with Consul and Vault.","fork":false,"url":"https://api.github.com/repos/hashicorp/nomad","forks_url":"https://api.github.com/repos/hashicorp/nomad/forks","keys_url":"https://api.github.com/repos/hashicorp/nomad/keys{/key_id}","collaborators_url":"https://api.github.com/repos/hashicorp/nomad/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/hashicorp/nomad/teams","hooks_url":"https://api.github.com/repos/hashicorp/nomad/hooks","issue_events_url":"https://api.github.com/repos/hashicorp/nomad/issues/events{/number}","events_url":"https://api.github.com/repos/hashicorp/nomad/events","assignees_url":"https://api.github.com/repos/hashicorp/nomad/assignees{/user}","branches_url":"https://api.github.com/repos/hashicorp/nomad/branches{/branch}","tags_url":"https://api.github.com/repos/hashicorp/nomad/tags","blobs_url":"https://api.github.com/repos/hashicorp/nomad/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/hashicorp/nomad/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/hashicorp/nomad/git/refs{/sha}","trees_url":"https://api.github.com/repos/hashicorp/nomad/git/trees{/sha}","statuses_url":"https://api.github.com/repos/hashicorp/nomad/statuses/{sha}","languages_url":"https://api.github.com/repos/hashicorp/nomad/languages","stargazers_url":"https://api.github.com/repos/hashicorp/nomad/stargazers","contributors_url":"https://api.github.com/repos/hashicorp/nomad/contributors","subscribers_url":"https://api.github.com/repos/hashicorp/nomad/subscribers","subscription_url":"https://api.github.com/repos/hashicorp/nomad/subscription","commits_url":"https://api.github.com/repos/hashicorp/nomad/commits{/sha}","git_commits_url":"https://api.github.com/repos/hashicorp/nomad/git/commits{/sha}","comments_url":"https://api.github.com/repos/hashicorp/nomad/comments{/number}","issue_comment_url":"https://api.github.com/repos/hashicorp/nomad/issues/comments{/number}","contents_url":"https://api.github.com/repos/hashicorp/nomad/contents/{+path}","compare_url":"https://api.github.com/repos/hashicorp/nomad/compare/{base}...{head}","merges_url":"https://api.github.com/repos/hashicorp/nomad/merges","archive_url":"https://api.github.com/repos/hashicorp/nomad/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/hashicorp/nomad/downloads","issues_url":"https://api.github.com/repos/hashicorp/nomad/issues{/number}","pulls_url":"https://api.github.com/repos/hashicorp/nomad/pulls{/number}","milestones_url":"https://api.github.com/repos/hashicorp/nomad/milestones{/number}","notifications_url":"https://api.github.com/repos/hashicorp/nomad/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/hashicorp/nomad/labels{/name}","releases_url":"https://api.github.com/repos/hashicorp/nomad/releases{/id}","deployments_url":"https://api.github.com/repos/hashicorp/nomad/deployments","created_at":"2015-06-01T10:21:00Z","updated_at":"2018-11-27T03:43:54Z","pushed_at":"2018-11-27T03:44:49Z","git_url":"git://github.com/hashicorp/nomad.git","ssh_url":"git@github.com:hashicorp/nomad.git","clone_url":"https://github.com/hashicorp/nomad.git","svn_url":"https://github.com/hashicorp/nomad","homepage":"https://www.nomadproject.io/","size":114855,"stargazers_count":4038,"watchers_count":4038,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":791,"mirror_url":null,"archived":false,"open_issues_count":461,"license":{"key":"mpl-2.0","name":"Mozilla Public License 2.0","spdx_id":"MPL-2.0","url":"https://api.github.com/licenses/mpl-2.0","node_id":"MDc6TGljZW5zZTE0"},"forks":791,"open_issues":461,"watchers":4038,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/hashicorp/nomad/pulls/4918"},"html":{"href":"https://github.com/hashicorp/nomad/pull/4918"},"issue":{"href":"https://api.github.com/repos/hashicorp/nomad/issues/4918"},"comments":{"href":"https://api.github.com/repos/hashicorp/nomad/issues/4918/comments"},"review_comments":{"href":"https://api.github.com/repos/hashicorp/nomad/pulls/4918/comments"},"review_comment":{"href":"https://api.github.com/repos/hashicorp/nomad/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/hashicorp/nomad/pulls/4918/commits"},"statuses":{"href":"https://api.github.com/repos/hashicorp/nomad/statuses/8544487eb66fbf9636eec2e3a1284731cf6bdb88"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"schmichael"}}