mirror of
https://github.com/rancher/cli.git
synced 2026-02-05 09:48:36 +01:00
cli 2.0
This commit is contained in:
101
cmd/catalog.go
101
cmd/catalog.go
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/go-rancher/catalog"
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@@ -67,15 +67,6 @@ func CatalogCommand() cli.Command {
|
||||
},
|
||||
},
|
||||
},
|
||||
/*
|
||||
cli.Command{
|
||||
Name: "upgrade",
|
||||
Usage: "Upgrade catalog template",
|
||||
Action: errorWrapper(envUpdate),
|
||||
ArgsUsage: "[ID or NAME]"
|
||||
Flags: []cli.Flag{},
|
||||
},
|
||||
*/
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -86,50 +77,6 @@ type CatalogData struct {
|
||||
Category string
|
||||
}
|
||||
|
||||
func getEnvFilter(proj *client.Project, ctx *cli.Context) string {
|
||||
envFilter := proj.Orchestration
|
||||
if envFilter == "cattle" {
|
||||
envFilter = ""
|
||||
}
|
||||
if ctx.Bool("system") {
|
||||
envFilter = "infra"
|
||||
}
|
||||
return envFilter
|
||||
}
|
||||
|
||||
func isInCSV(value, csv string) bool {
|
||||
for _, part := range strings.Split(csv, ",") {
|
||||
if value == part {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isOrchestrationSupported(ctx *cli.Context, proj *client.Project, labels map[string]interface{}) bool {
|
||||
// Only check for system templates
|
||||
if !ctx.Bool("system") {
|
||||
return true
|
||||
}
|
||||
|
||||
if supported, ok := labels[orchestrationSupported]; ok {
|
||||
supportedString := fmt.Sprint(supported)
|
||||
if supportedString != "" && !isInCSV(proj.Orchestration, supportedString) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func isSupported(ctx *cli.Context, proj *client.Project, item catalog.Template) bool {
|
||||
envFilter := getEnvFilter(proj, ctx)
|
||||
if item.TemplateBase != envFilter {
|
||||
return false
|
||||
}
|
||||
return isOrchestrationSupported(ctx, proj, item.Labels)
|
||||
}
|
||||
|
||||
func catalogLs(ctx *cli.Context) error {
|
||||
writer := NewTableWriter([][]string{
|
||||
{"NAME", "Template.Name"},
|
||||
@@ -153,7 +100,7 @@ func catalogLs(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
func forEachTemplate(ctx *cli.Context, f func(item catalog.Template) error) error {
|
||||
_, c, proj, cc, err := setupCatalogContext(ctx)
|
||||
_, c, _, cc, err := setupCatalogContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -169,9 +116,6 @@ func forEachTemplate(ctx *cli.Context, f func(item catalog.Template) error) erro
|
||||
}
|
||||
|
||||
for _, item := range collection.Data {
|
||||
if !isSupported(ctx, proj, item) {
|
||||
continue
|
||||
}
|
||||
if err := f(item); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -234,7 +178,7 @@ func catalogInstall(ctx *cli.Context) error {
|
||||
return errors.New("Exactly one argument is required")
|
||||
}
|
||||
|
||||
_, c, proj, cc, err := setupCatalogContext(ctx)
|
||||
_, c, _, cc, err := setupCatalogContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -269,34 +213,15 @@ func catalogInstall(ctx *cli.Context) error {
|
||||
|
||||
externalID := fmt.Sprintf("catalog://%s", templateVersion.Id)
|
||||
id := ""
|
||||
switch proj.Orchestration {
|
||||
case "cattle":
|
||||
stack, err := c.Stack.Create(&client.Stack{
|
||||
Name: stackName,
|
||||
DockerCompose: toString(templateVersion.Files["docker-compose.yml"]),
|
||||
RancherCompose: toString(templateVersion.Files["rancher-compose.yml"]),
|
||||
Environment: answers,
|
||||
ExternalId: externalID,
|
||||
System: ctx.Bool("system"),
|
||||
StartOnCreate: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id = stack.Id
|
||||
case "kubernetes":
|
||||
stack, err := c.KubernetesStack.Create(&client.KubernetesStack{
|
||||
Name: stackName,
|
||||
Templates: templateVersion.Files,
|
||||
ExternalId: externalID,
|
||||
Environment: answers,
|
||||
System: ctx.Bool("system"),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id = stack.Id
|
||||
stack, err := c.Stack.Create(&client.Stack{
|
||||
Name: stackName,
|
||||
Templates: templateVersion.Files,
|
||||
ExternalId: externalID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id = stack.Id
|
||||
|
||||
return WaitFor(ctx, id)
|
||||
}
|
||||
@@ -392,11 +317,11 @@ func GetCatalogClient(ctx *cli.Context) (*catalog.RancherClient, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
idx := strings.LastIndex(config.URL, "/v2-beta")
|
||||
idx := strings.LastIndex(config.URL, "/v3")
|
||||
if idx == -1 {
|
||||
idx = strings.LastIndex(config.URL, "/v1")
|
||||
if idx == -1 {
|
||||
return nil, fmt.Errorf("Invalid URL %s, must contain /v2-beta", config.URL)
|
||||
return nil, fmt.Errorf("Invalid URL %s, must contain /v3", config.URL)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"syscall"
|
||||
"text/template"
|
||||
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
|
||||
"github.com/docker/docker/pkg/namesgenerator"
|
||||
"github.com/urfave/cli"
|
||||
@@ -32,7 +32,7 @@ func GetRawClient(ctx *cli.Context) (*client.RancherClient, error) {
|
||||
return nil, err
|
||||
}
|
||||
return client.NewRancherClient(&client.ClientOpts{
|
||||
Url: url + "/v2-beta",
|
||||
Url: url + "/v3",
|
||||
AccessKey: config.AccessKey,
|
||||
SecretKey: config.SecretKey,
|
||||
})
|
||||
@@ -93,7 +93,11 @@ func GetClient(ctx *cli.Context) (*client.RancherClient, error) {
|
||||
}
|
||||
|
||||
func GetEnvironment(def string, c *client.RancherClient) (*client.Project, error) {
|
||||
resp, err := c.Project.List(nil)
|
||||
resp, err := c.Project.List(&client.ListOpts{
|
||||
Filters: map[string]interface{}{
|
||||
"all": true,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -109,7 +113,7 @@ func GetEnvironment(def string, c *client.RancherClient) (*client.Project, error
|
||||
if def == "" {
|
||||
names := []string{}
|
||||
for _, p := range resp.Data {
|
||||
names = append(names, fmt.Sprintf("%s(%s)", p.Name, p.Id))
|
||||
names = append(names, fmt.Sprintf("%s(%s), cluster ID: (%s)", p.Name, p.Id, p.ClusterId))
|
||||
}
|
||||
|
||||
idx := selectFromList("Environments:", names)
|
||||
@@ -174,6 +178,24 @@ func RandomName() string {
|
||||
return strings.Replace(namesgenerator.GetRandomName(0), "_", "-", -1)
|
||||
}
|
||||
|
||||
func getContainerByName(c *client.RancherClient, name string) (client.ResourceCollection, error) {
|
||||
var result client.ResourceCollection
|
||||
stack, containerName, err := ParseName(c, name)
|
||||
containers, err := c.Container.List(&client.ListOpts{
|
||||
Filters: map[string]interface{}{
|
||||
"stackId": stack.Id,
|
||||
"name": containerName,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
for _, container := range containers.Data {
|
||||
result.Data = append(result.Data, container.Resource)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func getServiceByName(c *client.RancherClient, name string) (client.ResourceCollection, error) {
|
||||
var result client.ResourceCollection
|
||||
stack, serviceName, err := ParseName(c, name)
|
||||
@@ -219,9 +241,10 @@ func Lookup(c *client.RancherClient, name string, types ...string) (*client.Reso
|
||||
if len(collection.Data) > 1 {
|
||||
ids := []string{}
|
||||
for _, data := range collection.Data {
|
||||
ids = append(ids, data.Id)
|
||||
ids = append(ids, fmt.Sprintf("%s (%s)", data.Id, name))
|
||||
}
|
||||
return nil, fmt.Errorf("Multiple resources of type %s found for name %s: %v", schemaType, name, ids)
|
||||
index := selectFromList("Resources: ", ids)
|
||||
return &collection.Data[index], nil
|
||||
}
|
||||
|
||||
if len(collection.Data) == 0 {
|
||||
@@ -232,6 +255,8 @@ func Lookup(c *client.RancherClient, name string, types ...string) (*client.Reso
|
||||
collection, err = getHostByHostname(c, name)
|
||||
case "service":
|
||||
collection, err = getServiceByName(c, name)
|
||||
case "container":
|
||||
collection, err = getContainerByName(c, name)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/rancher/rancher-docker-api-proxy"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
275
cmd/env.go
275
cmd/env.go
@@ -2,12 +2,10 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func EnvCommand() cli.Command {
|
||||
@@ -30,7 +28,7 @@ func EnvCommand() cli.Command {
|
||||
Action: defaultAction(envLs),
|
||||
Flags: envLsFlags,
|
||||
Subcommands: []cli.Command{
|
||||
cli.Command{
|
||||
{
|
||||
Name: "ls",
|
||||
Usage: "List environments",
|
||||
Description: "\nWith an account API key, all environments in Rancher will be listed. If you are using an environment API key, it will only list the environment of the API key. \n\nExample:\n\t$ rancher env ls\n",
|
||||
@@ -38,7 +36,7 @@ func EnvCommand() cli.Command {
|
||||
Action: envLs,
|
||||
Flags: envLsFlags,
|
||||
},
|
||||
cli.Command{
|
||||
{
|
||||
Name: "create",
|
||||
Usage: "Create an environment",
|
||||
Description: `
|
||||
@@ -57,41 +55,13 @@ To add an orchestration framework do TODO
|
||||
Action: envCreate,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "template,t",
|
||||
Usage: "Environment template to create from",
|
||||
Value: "Cattle",
|
||||
Name: "cluster,c",
|
||||
Usage: "Cluster name to create the environment",
|
||||
Value: "Default",
|
||||
},
|
||||
},
|
||||
},
|
||||
cli.Command{
|
||||
Name: "templates",
|
||||
ShortName: "template",
|
||||
Usage: "Interact with environment templates",
|
||||
Action: defaultAction(envTemplateLs),
|
||||
Flags: envLsFlags,
|
||||
Subcommands: []cli.Command{
|
||||
cli.Command{
|
||||
Name: "export",
|
||||
Usage: "Export an environment template to STDOUT",
|
||||
ArgsUsage: "[TEMPLATEID TEMPLATENAME...]",
|
||||
Action: envTemplateExport,
|
||||
Flags: []cli.Flag{},
|
||||
},
|
||||
cli.Command{
|
||||
Name: "import",
|
||||
Usage: "Import an environment template to from file",
|
||||
ArgsUsage: "[FILE FILE...]",
|
||||
Action: envTemplateImport,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "public",
|
||||
Usage: "Make template public",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cli.Command{
|
||||
{
|
||||
Name: "rm",
|
||||
Usage: "Remove environment(s)",
|
||||
Description: "\nExample:\n\t$ rancher env rm 1a5\n\t$ rancher env rm newEnv\n",
|
||||
@@ -99,7 +69,7 @@ To add an orchestration framework do TODO
|
||||
Action: envRm,
|
||||
Flags: []cli.Flag{},
|
||||
},
|
||||
cli.Command{
|
||||
{
|
||||
Name: "deactivate",
|
||||
Usage: "Deactivate environment(s)",
|
||||
Description: `
|
||||
@@ -113,7 +83,7 @@ Example:
|
||||
Action: envDeactivate,
|
||||
Flags: []cli.Flag{},
|
||||
},
|
||||
cli.Command{
|
||||
{
|
||||
Name: "activate",
|
||||
Usage: "Activate environment(s)",
|
||||
Description: `
|
||||
@@ -127,6 +97,20 @@ Example:
|
||||
Action: envActivate,
|
||||
Flags: []cli.Flag{},
|
||||
},
|
||||
{
|
||||
Name: "switch",
|
||||
Usage: "Switch environment(s)",
|
||||
Description: `
|
||||
Switch current environment to others,
|
||||
|
||||
Example:
|
||||
$ rancher env switch 1a5
|
||||
$ rancher env switch Default
|
||||
`,
|
||||
ArgsUsage: "[ID NAME...]",
|
||||
Action: envSwitch,
|
||||
Flags: []cli.Flag{},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -134,17 +118,20 @@ Example:
|
||||
type EnvData struct {
|
||||
ID string
|
||||
Environment *client.Project
|
||||
Current string
|
||||
Name string
|
||||
}
|
||||
|
||||
type TemplateData struct {
|
||||
ID string
|
||||
ProjectTemplate *client.ProjectTemplate
|
||||
}
|
||||
|
||||
func NewEnvData(project client.Project) *EnvData {
|
||||
func NewEnvData(project client.Project, current bool, name string) *EnvData {
|
||||
marked := ""
|
||||
if current {
|
||||
marked = " *"
|
||||
}
|
||||
return &EnvData{
|
||||
ID: project.Id,
|
||||
Environment: &project,
|
||||
Current: marked,
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,18 +156,22 @@ func envCreate(ctx *cli.Context) error {
|
||||
if ctx.NArg() > 0 {
|
||||
name = ctx.Args()[0]
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"name": name,
|
||||
clusters, err := c.Cluster.List(&client.ListOpts{
|
||||
Filters: map[string]interface{}{
|
||||
"name": ctx.String("cluster"),
|
||||
"removed_null": true,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
template := ctx.String("template")
|
||||
if template != "" {
|
||||
template, err := Lookup(c, template, "projectTemplate")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data["projectTemplateId"] = template.Id
|
||||
if len(clusters.Data) == 0 {
|
||||
return errors.Errorf("can't find cluster with name %v", ctx.String("cluster"))
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"name": name,
|
||||
"clusterId": clusters.Data[0].Id,
|
||||
}
|
||||
|
||||
var newEnv client.Project
|
||||
@@ -197,23 +188,46 @@ func envLs(ctx *cli.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config, err := lookupConfig(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentEnvID := config.Environment
|
||||
|
||||
writer := NewTableWriter([][]string{
|
||||
{"ID", "ID"},
|
||||
{"NAME", "Environment.Name"},
|
||||
{"ORCHESTRATION", "Environment.Orchestration"},
|
||||
{"CLUSTER/NAME", "Name"},
|
||||
{"STATE", "Environment.State"},
|
||||
{"CREATED", "Environment.Created"},
|
||||
{"CURRENT", "Current"},
|
||||
}, ctx)
|
||||
defer writer.Close()
|
||||
|
||||
collection, err := c.Project.List(defaultListOpts(ctx))
|
||||
listOpts := defaultListOpts(ctx)
|
||||
listOpts.Filters["all"] = true
|
||||
collection, err := c.Project.List(listOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range collection.Data {
|
||||
writer.Write(NewEnvData(item))
|
||||
current := false
|
||||
if item.Id == currentEnvID {
|
||||
current = true
|
||||
}
|
||||
clusterName := ""
|
||||
if item.ClusterId != "" {
|
||||
cluster, err := c.Cluster.ById(item.ClusterId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clusterName = cluster.Name
|
||||
}
|
||||
name := item.Name
|
||||
if clusterName != "" {
|
||||
name = fmt.Sprintf("%s/%s", clusterName, name)
|
||||
}
|
||||
writer.Write(NewEnvData(item, current, name))
|
||||
}
|
||||
|
||||
return writer.Err()
|
||||
@@ -249,119 +263,48 @@ func envActivate(ctx *cli.Context) error {
|
||||
})
|
||||
}
|
||||
|
||||
func envTemplateLs(ctx *cli.Context) error {
|
||||
func envSwitch(ctx *cli.Context) error {
|
||||
c, err := GetRawClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
writer := NewTableWriter([][]string{
|
||||
{"ID", "ID"},
|
||||
{"NAME", "ProjectTemplate.Name"},
|
||||
{"DESC", "ProjectTemplate.Description"},
|
||||
}, ctx)
|
||||
defer writer.Close()
|
||||
|
||||
collection, err := c.ProjectTemplate.List(defaultListOpts(ctx))
|
||||
if ctx.NArg() == 0 {
|
||||
return cli.ShowCommandHelp(ctx, "env")
|
||||
}
|
||||
envID := ""
|
||||
name := ctx.Args()[0]
|
||||
if env, err := c.Project.ById(name); err == nil && env != nil && env.Id == name {
|
||||
envID = name
|
||||
} else {
|
||||
if envs, err := c.Project.List(&client.ListOpts{
|
||||
Filters: map[string]interface{}{
|
||||
"name": name,
|
||||
},
|
||||
}); err == nil {
|
||||
if len(envs.Data) == 1 {
|
||||
envID = envs.Data[0].Id
|
||||
} else if len(envs.Data) > 1 {
|
||||
names := []string{}
|
||||
for _, item := range envs.Data {
|
||||
names = append(names, fmt.Sprintf("%s(%s/%s)", item.Name, item.ClusterId, item.Id))
|
||||
}
|
||||
idx := selectFromList("Found multiple environments in different clusters:", names)
|
||||
envID = envs.Data[idx].Id
|
||||
}
|
||||
}
|
||||
}
|
||||
if envID == "" {
|
||||
return cli.NewExitError("Error: can't find associated environment", 1)
|
||||
}
|
||||
config, err := lookupConfig(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range collection.Data {
|
||||
writer.Write(TemplateData{
|
||||
ID: item.Id,
|
||||
ProjectTemplate: &item,
|
||||
})
|
||||
config.Environment = envID
|
||||
err = config.Write()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writer.Err()
|
||||
}
|
||||
|
||||
func envTemplateImport(ctx *cli.Context) error {
|
||||
c, err := GetRawClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w, err := NewWaiter(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range ctx.Args() {
|
||||
template := client.ProjectTemplate{
|
||||
IsPublic: ctx.Bool("public"),
|
||||
}
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(content, &template); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
created, err := c.ProjectTemplate.Create(&template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Add(created.Id)
|
||||
}
|
||||
|
||||
return w.Wait()
|
||||
}
|
||||
|
||||
func envTemplateExport(ctx *cli.Context) error {
|
||||
c, err := GetRawClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, name := range ctx.Args() {
|
||||
r, err := Lookup(c, name, "projectTemplate")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
template, err := c.ProjectTemplate.ById(r.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stacks := []map[string]interface{}{}
|
||||
for _, s := range template.Stacks {
|
||||
data := map[string]interface{}{
|
||||
"name": s.Name,
|
||||
}
|
||||
if s.TemplateId != "" {
|
||||
data["template_id"] = s.TemplateId
|
||||
}
|
||||
if s.TemplateVersionId != "" {
|
||||
data["template_version_id"] = s.TemplateVersionId
|
||||
}
|
||||
if len(s.Answers) > 0 {
|
||||
data["answers"] = s.Answers
|
||||
}
|
||||
stacks = append(stacks, data)
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"name": template.Name,
|
||||
"description": template.Description,
|
||||
"stacks": stacks,
|
||||
}
|
||||
|
||||
content, err := yaml.Marshal(&data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = os.Stdout.Write(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return envLs(ctx)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/rancher/cli/monitor"
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
||||
13
cmd/exec.go
13
cmd/exec.go
@@ -8,7 +8,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@@ -90,7 +90,7 @@ func selectContainer(c *client.RancherClient, args []string) ([]string, string,
|
||||
return nil, "", "", err
|
||||
}
|
||||
|
||||
if _, ok := resource.Links["hosts"]; ok {
|
||||
if _, ok := resource.Links["host"]; ok {
|
||||
hostID, containerID, err := getHostnameAndContainerID(c, resource.Id)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
@@ -170,16 +170,15 @@ func getHostnameAndContainerID(c *client.RancherClient, containerID string) (str
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var hosts client.HostCollection
|
||||
if err := c.GetLink(container.Resource, "hosts", &hosts); err != nil {
|
||||
var host client.Host
|
||||
if err := c.GetLink(container.Resource, "host", &host); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if len(hosts.Data) != 1 {
|
||||
if host.Id == "" {
|
||||
return "", "", fmt.Errorf("Failed to find host for container %s", container.Name)
|
||||
}
|
||||
|
||||
return hosts.Data[0].Id, container.ExternalId, nil
|
||||
return host.Id, container.ExternalId, nil
|
||||
}
|
||||
|
||||
func runDockerHelp(subcommand string) error {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@@ -111,10 +111,7 @@ func exportService(ctx *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := addToTar(archive, stack.Name, "docker-compose.yml", config.DockerComposeConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := addToTar(archive, stack.Name, "rancher-compose.yml", config.RancherComposeConfig); err != nil {
|
||||
if err := addToTar(archive, stack.Name, "compose.yml", config.DockerComposeConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(config.Actions) > 0 {
|
||||
|
||||
@@ -4,9 +4,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
)
|
||||
|
||||
func FormatEndpoint(data interface{}) string {
|
||||
@@ -35,19 +32,21 @@ func FormatEndpoint(data interface{}) string {
|
||||
}
|
||||
|
||||
func FormatIPAddresses(data interface{}) string {
|
||||
ips, ok := data.([]client.IpAddress)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
ipStrings := []string{}
|
||||
for _, ip := range ips {
|
||||
if ip.Address != "" {
|
||||
ipStrings = append(ipStrings, ip.Address)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(ipStrings, ", ")
|
||||
//todo: revisit
|
||||
return ""
|
||||
//ips, ok := data.([]client.IpAddress)
|
||||
//if !ok {
|
||||
// return ""
|
||||
//}
|
||||
//
|
||||
//ipStrings := []string{}
|
||||
//for _, ip := range ips {
|
||||
// if ip.Address != "" {
|
||||
// ipStrings = append(ipStrings, ip.Address)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//return strings.Join(ipStrings, ", ")
|
||||
}
|
||||
|
||||
func FormatJSON(data interface{}) (string, error) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@@ -76,7 +76,7 @@ func getLabels(host *client.Host) string {
|
||||
|
||||
buffer.WriteString(key)
|
||||
buffer.WriteString("=")
|
||||
buffer.WriteString(value.(string))
|
||||
buffer.WriteString(value)
|
||||
it++
|
||||
}
|
||||
return buffer.String()
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@@ -282,12 +282,12 @@ func hostCreateRun(ctx *cli.Context, c *client.RancherClient, machineSchema clie
|
||||
var lastErr error
|
||||
for _, name := range names {
|
||||
args["hostname"] = name
|
||||
var machine client.Machine
|
||||
if err := c.Create("host", args, &machine); err != nil {
|
||||
var host client.Host
|
||||
if err := c.Create("host", args, &host); err != nil {
|
||||
lastErr = err
|
||||
logrus.Error(err)
|
||||
} else {
|
||||
w.Add(machine.Id)
|
||||
w.Add(host.Id)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package cmd
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"github.com/docker/libcompose/cli/logger"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rancher/cli/monitor"
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/rancher/rancher-docker-api-proxy"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
34
cmd/prompt.go
Normal file
34
cmd/prompt.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/c-bata/go-prompt"
|
||||
"github.com/rancher/cli/rancher_prompt"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func PromptCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "prompt",
|
||||
Usage: "Enter rancher cli auto-prompt mode",
|
||||
ArgsUsage: "None",
|
||||
Action: promptAction,
|
||||
Flags: []cli.Flag{},
|
||||
}
|
||||
}
|
||||
|
||||
func promptAction(ctx *cli.Context) error {
|
||||
fmt.Print("rancher cli auto-completion mode")
|
||||
defer fmt.Println("Goodbye!")
|
||||
p := prompt.New(
|
||||
rancherPrompt.Executor,
|
||||
rancherPrompt.Completer,
|
||||
prompt.OptionTitle("rancher-prompt: interactive rancher client"),
|
||||
prompt.OptionPrefix("rancher$ "),
|
||||
prompt.OptionInputTextColor(prompt.Yellow),
|
||||
prompt.OptionMaxSuggestion(20),
|
||||
)
|
||||
p.Run()
|
||||
return nil
|
||||
}
|
||||
43
cmd/ps.go
43
cmd/ps.go
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@@ -50,7 +50,7 @@ func GetStackMap(c *client.RancherClient) map[string]client.Stack {
|
||||
}
|
||||
|
||||
type PsData struct {
|
||||
Service client.Service
|
||||
Service interface{}
|
||||
Name string
|
||||
LaunchConfig interface{}
|
||||
Stack client.Stack
|
||||
@@ -86,6 +86,11 @@ func servicePs(ctx *cli.Context) error {
|
||||
return errors.Wrap(err, "service list failed")
|
||||
}
|
||||
|
||||
collectionContainers, err := c.Container.List(defaultListOpts(ctx))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "container list failed")
|
||||
}
|
||||
|
||||
writer := NewTableWriter([][]string{
|
||||
{"ID", "Service.Id"},
|
||||
{"TYPE", "Service.Type"},
|
||||
@@ -93,8 +98,7 @@ func servicePs(ctx *cli.Context) error {
|
||||
{"IMAGE", "LaunchConfig.ImageUuid"},
|
||||
{"STATE", "CombinedState"},
|
||||
{"SCALE", "{{len .Service.InstanceIds}}/{{.Service.Scale}}"},
|
||||
{"SYSTEM", "Service.System"},
|
||||
{"ENDPOINTS", "{{endpoint .Service.PublicEndpoints}}"},
|
||||
{"ENDPOINTS", "{{range .Service.PublicEndpoints}}{{.AgentIpAddress}}:{{.PublicPort}}:{{.PrivatePort}}/{{.Protocol}} {{end}}"},
|
||||
{"DETAIL", "Service.TransitioningMessage"},
|
||||
}, ctx)
|
||||
|
||||
@@ -104,6 +108,7 @@ func servicePs(ctx *cli.Context) error {
|
||||
if item.LaunchConfig != nil {
|
||||
item.LaunchConfig.ImageUuid = strings.TrimPrefix(item.LaunchConfig.ImageUuid, "docker:")
|
||||
}
|
||||
item.Type = "service"
|
||||
|
||||
combined := item.HealthState
|
||||
if item.State != "active" || combined == "" {
|
||||
@@ -135,6 +140,34 @@ func servicePs(ctx *cli.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range collectionContainers.Data {
|
||||
if len(item.ServiceIds) == 0 && item.StackId != "" {
|
||||
launchConfig := client.LaunchConfig{}
|
||||
launchConfig.ImageUuid = item.Image
|
||||
|
||||
service := client.Service{}
|
||||
service.Id = item.Id
|
||||
service.Type = "standalone"
|
||||
service.InstanceIds = []string{item.Id}
|
||||
service.Scale = 1
|
||||
service.PublicEndpoints = item.PublicEndpoints
|
||||
service.TransitioningMessage = item.TransitioningMessage
|
||||
|
||||
combined := item.HealthState
|
||||
if item.State != "active" || combined == "" {
|
||||
combined = item.State
|
||||
}
|
||||
writer.Write(PsData{
|
||||
ID: item.Id,
|
||||
Service: service,
|
||||
Name: fmt.Sprintf("%s/%s", stackMap[item.StackId].Name, item.Name),
|
||||
LaunchConfig: launchConfig,
|
||||
Stack: stackMap[item.StackId],
|
||||
CombinedState: combined,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return writer.Err()
|
||||
}
|
||||
|
||||
@@ -193,7 +226,7 @@ func containerPs(ctx *cli.Context, containers []client.Container) error {
|
||||
{"STATE", "CombinedState"},
|
||||
{"HOST", "Container.HostId"},
|
||||
{"IP", "Container.PrimaryIpAddress"},
|
||||
{"DOCKER", "DockerID"},
|
||||
{"DOCKER_ID", "DockerID"},
|
||||
{"DETAIL", "Container.TransitioningMessage"},
|
||||
//TODO: {"PORTS", "{{ports .Container.Ports}}"},
|
||||
}, ctx)
|
||||
|
||||
72
cmd/pull.go
Normal file
72
cmd/pull.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fatih/color"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
"time"
|
||||
)
|
||||
|
||||
func PullCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "pull",
|
||||
Usage: "Pull images on hosts that are in the current environment. Examples: rancher pull ubuntu",
|
||||
Action: pullImages,
|
||||
Subcommands: []cli.Command{},
|
||||
Flags: []cli.Flag{
|
||||
cli.StringSliceFlag{
|
||||
Name: "hosts",
|
||||
Usage: "Specify which host should pull images. By default it will pull images on all the hosts in the current environment. Examples: rancher pull --hosts 1h1 --hosts 1h2 ubuntu",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func pullImages(ctx *cli.Context) error {
|
||||
c, err := GetClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.NArg() == 0 {
|
||||
return cli.ShowCommandHelp(ctx, "")
|
||||
}
|
||||
image := ctx.Args()[0]
|
||||
|
||||
hosts := ctx.StringSlice("hosts")
|
||||
if len(hosts) == 0 {
|
||||
hts, err := c.Host.List(defaultListOpts(ctx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, ht := range hts.Data {
|
||||
hosts = append(hosts, ht.Id)
|
||||
}
|
||||
}
|
||||
pullTask := client.PullTask{
|
||||
Mode: "all",
|
||||
Image: image,
|
||||
HostIds: hosts,
|
||||
}
|
||||
task, err := c.PullTask.Create(&pullTask)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cl := getRandomColor()
|
||||
lastMsg := ""
|
||||
for {
|
||||
if task.Transitioning != "yes" {
|
||||
fmt.Printf("Finished pulling image %s\n", image)
|
||||
return nil
|
||||
}
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
if task.TransitioningMessage != lastMsg {
|
||||
color.New(cl).Printf("Pulling image. Status: %s\n", task.TransitioningMessage)
|
||||
lastMsg = task.TransitioningMessage
|
||||
}
|
||||
task, err = c.PullTask.ById(task.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package cmd
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@@ -40,12 +40,8 @@ func restartResources(ctx *cli.Context) error {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = c.Action(resource.Type, action, resource, &client.ServiceRestart{
|
||||
RollingRestartStrategy: client.RollingRestartStrategy{
|
||||
BatchSize: int64(ctx.Int("batch-size")),
|
||||
IntervalMillis: int64(ctx.Int("interval")),
|
||||
},
|
||||
}, resource)
|
||||
//todo: revisit restart policy
|
||||
err = c.Action(resource.Type, action, resource, nil, resource)
|
||||
return resource.Id, err
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
||||
246
cmd/run.go
246
cmd/run.go
@@ -3,7 +3,7 @@ package cmd
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -302,29 +302,173 @@ func serviceRun(ctx *cli.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
launchConfig := &client.LaunchConfig{
|
||||
|
||||
if ctx.IsSet("scale") {
|
||||
launchConfig := &client.LaunchConfig{
|
||||
//BlkioDeviceOptions:
|
||||
BlkioWeight: ctx.Int64("blkio-weight"),
|
||||
CapAdd: ctx.StringSlice("cap-add"),
|
||||
CapDrop: ctx.StringSlice("cap-drop"),
|
||||
//CpuSet: ctx.String(""),
|
||||
CgroupParent: ctx.String("cgroup-parent"),
|
||||
CpuSetMems: ctx.String("cpuset-mems"),
|
||||
CpuPeriod: ctx.Int64("cpu-period"),
|
||||
CpuQuota: ctx.Int64("cpu-quota"),
|
||||
CpuShares: ctx.Int64("cpu-shares"),
|
||||
Devices: ctx.StringSlice("device"),
|
||||
Dns: ctx.StringSlice("dns"),
|
||||
DnsOpt: ctx.StringSlice("dns-opt"),
|
||||
DnsSearch: ctx.StringSlice("dns-search"),
|
||||
EntryPoint: ctx.StringSlice("entrypoint"),
|
||||
Expose: ctx.StringSlice("expose"),
|
||||
GroupAdd: ctx.StringSlice("group-add"),
|
||||
HealthCmd: ctx.StringSlice("health-cmd"),
|
||||
HealthTimeout: ctx.Int64("health-timeout"),
|
||||
HealthInterval: ctx.Int64("health-interval"),
|
||||
HealthRetries: ctx.Int64("health-retries"),
|
||||
Hostname: ctx.String("hostname"),
|
||||
ImageUuid: "docker:" + ctx.Args()[0],
|
||||
Ip: ctx.String("ip"),
|
||||
Ip6: ctx.String("ip6"),
|
||||
IpcMode: ctx.String("ipc"),
|
||||
Isolation: ctx.String("isolation"),
|
||||
KernelMemory: ctx.Int64("kernel-memory"),
|
||||
Labels: map[string]string{},
|
||||
Environment: map[string]string{},
|
||||
//LogConfig:
|
||||
Memory: ctx.Int64("memory"),
|
||||
MemoryReservation: ctx.Int64("memory-reservation"),
|
||||
MemorySwap: ctx.Int64("memory-swap"),
|
||||
MemorySwappiness: ctx.Int64("memory-swappiness"),
|
||||
//NetworkIds: ctx.StringSlice("networkids"),
|
||||
NetAlias: ctx.StringSlice("net-alias"),
|
||||
NetworkMode: ctx.String("net"),
|
||||
OomKillDisable: ctx.Bool("oom-kill-disable"),
|
||||
OomScoreAdj: ctx.Int64("oom-score-adj"),
|
||||
PidMode: ctx.String("pid"),
|
||||
PidsLimit: ctx.Int64("pids-limit"),
|
||||
Ports: ctx.StringSlice("publish"),
|
||||
Privileged: ctx.Bool("privileged"),
|
||||
PublishAllPorts: ctx.Bool("publish-all"),
|
||||
ReadOnly: ctx.Bool("read-only"),
|
||||
//todo: add RunInit
|
||||
//RunInit: ctx.Bool("init"),
|
||||
SecurityOpt: ctx.StringSlice("security-opt"),
|
||||
ShmSize: ctx.Int64("shm-size"),
|
||||
StdinOpen: ctx.Bool("interactive"),
|
||||
StopSignal: ctx.String("stop-signal"),
|
||||
Tty: ctx.Bool("tty"),
|
||||
User: ctx.String("user"),
|
||||
Uts: ctx.String("uts"),
|
||||
VolumeDriver: ctx.String("volume-driver"),
|
||||
WorkingDir: ctx.String("workdir"),
|
||||
DataVolumes: ctx.StringSlice("volume"),
|
||||
}
|
||||
|
||||
if ctx.String("log-driver") != "" || len(ctx.StringSlice("log-opt")) > 0 {
|
||||
launchConfig.LogConfig = &client.LogConfig{
|
||||
Driver: ctx.String("log-driver"),
|
||||
Config: map[string]string{},
|
||||
}
|
||||
for _, opt := range ctx.StringSlice("log-opt") {
|
||||
parts := strings.SplitN(opt, "=", 2)
|
||||
if len(parts) > 1 {
|
||||
launchConfig.LogConfig.Config[parts[0]] = parts[1]
|
||||
} else {
|
||||
launchConfig.LogConfig.Config[parts[0]] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, label := range ctx.StringSlice("label") {
|
||||
parts := strings.SplitN(label, "=", 2)
|
||||
value := ""
|
||||
if len(parts) > 1 {
|
||||
value = parts[1]
|
||||
}
|
||||
launchConfig.Labels[parts[0]] = value
|
||||
}
|
||||
|
||||
for _, env := range ctx.StringSlice("env") {
|
||||
parts := strings.SplitN(env, "=", 2)
|
||||
value := ""
|
||||
|
||||
if len(parts) > 1 {
|
||||
value = parts[1]
|
||||
|
||||
if parts[0] == "" {
|
||||
errMsg := fmt.Sprintf("invalid argument \"%s\" for e: invalid environment variable: %s\nSee 'rancher run --help'.", env, env)
|
||||
return cli.NewExitError(errMsg, 1)
|
||||
}
|
||||
} else if len(parts) == 1 {
|
||||
value = os.Getenv(parts[0])
|
||||
}
|
||||
launchConfig.Environment[parts[0]] = value
|
||||
}
|
||||
|
||||
if ctx.Bool("schedule-global") {
|
||||
launchConfig.Labels["io.rancher.scheduler.global"] = "true"
|
||||
}
|
||||
|
||||
if ctx.Bool("pull") {
|
||||
launchConfig.Labels["io.rancher.container.pull_image"] = "always"
|
||||
}
|
||||
|
||||
args := ctx.Args()[1:]
|
||||
|
||||
if len(args) > 0 {
|
||||
launchConfig.Command = args
|
||||
}
|
||||
|
||||
stack, name, err := ParseName(c, ctx.String("name"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service := &client.Service{
|
||||
Name: name,
|
||||
StackId: stack.Id,
|
||||
LaunchConfig: launchConfig,
|
||||
Scale: int64(ctx.Int("scale")),
|
||||
}
|
||||
|
||||
service, err = c.Service.Create(service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return WaitFor(ctx, service.Id)
|
||||
}
|
||||
container := &client.Container{
|
||||
//BlkioDeviceOptions:
|
||||
BlkioWeight: ctx.Int64("blkio-weight"),
|
||||
CapAdd: ctx.StringSlice("cap-add"),
|
||||
CapDrop: ctx.StringSlice("cap-drop"),
|
||||
//CpuSet: ctx.String(""),
|
||||
CgroupParent: ctx.String("cgroup-parent"),
|
||||
CpuSetMems: ctx.String("cpuset-mems"),
|
||||
CpuPeriod: ctx.Int64("cpu-period"),
|
||||
CpuQuota: ctx.Int64("cpu-quota"),
|
||||
CpuShares: ctx.Int64("cpu-shares"),
|
||||
Devices: ctx.StringSlice("device"),
|
||||
Dns: ctx.StringSlice("dns"),
|
||||
DnsOpt: ctx.StringSlice("dns-opt"),
|
||||
DnsSearch: ctx.StringSlice("dns-search"),
|
||||
EntryPoint: ctx.StringSlice("entrypoint"),
|
||||
Expose: ctx.StringSlice("expose"),
|
||||
GroupAdd: ctx.StringSlice("group-add"),
|
||||
Hostname: ctx.String("hostname"),
|
||||
ImageUuid: "docker:" + ctx.Args()[0],
|
||||
KernelMemory: ctx.Int64("kernel-memory"),
|
||||
Labels: map[string]interface{}{},
|
||||
Environment: map[string]interface{}{},
|
||||
CgroupParent: ctx.String("cgroup-parent"),
|
||||
CpuSetMems: ctx.String("cpuset-mems"),
|
||||
CpuPeriod: ctx.Int64("cpu-period"),
|
||||
CpuQuota: ctx.Int64("cpu-quota"),
|
||||
CpuShares: ctx.Int64("cpu-shares"),
|
||||
Devices: ctx.StringSlice("device"),
|
||||
Dns: ctx.StringSlice("dns"),
|
||||
DnsOpt: ctx.StringSlice("dns-opt"),
|
||||
DnsSearch: ctx.StringSlice("dns-search"),
|
||||
EntryPoint: ctx.StringSlice("entrypoint"),
|
||||
Expose: ctx.StringSlice("expose"),
|
||||
GroupAdd: ctx.StringSlice("group-add"),
|
||||
HealthCmd: ctx.StringSlice("health-cmd"),
|
||||
HealthTimeout: ctx.Int64("health-timeout"),
|
||||
HealthInterval: ctx.Int64("health-interval"),
|
||||
HealthRetries: ctx.Int64("health-retries"),
|
||||
Hostname: ctx.String("hostname"),
|
||||
ImageUuid: "docker:" + ctx.Args()[0],
|
||||
Ip: ctx.String("ip"),
|
||||
Ip6: ctx.String("ip6"),
|
||||
IpcMode: ctx.String("ipc"),
|
||||
Isolation: ctx.String("isolation"),
|
||||
KernelMemory: ctx.Int64("kernel-memory"),
|
||||
Labels: map[string]string{},
|
||||
Environment: map[string]string{},
|
||||
//LogConfig:
|
||||
Memory: ctx.Int64("memory"),
|
||||
MemoryReservation: ctx.Int64("memory-reservation"),
|
||||
@@ -340,30 +484,35 @@ func serviceRun(ctx *cli.Context) error {
|
||||
Privileged: ctx.Bool("privileged"),
|
||||
PublishAllPorts: ctx.Bool("publish-all"),
|
||||
ReadOnly: ctx.Bool("read-only"),
|
||||
RunInit: ctx.Bool("init"),
|
||||
SecurityOpt: ctx.StringSlice("security-opt"),
|
||||
ShmSize: ctx.Int64("shm-size"),
|
||||
StdinOpen: ctx.Bool("interactive"),
|
||||
StopSignal: ctx.String("stop-signal"),
|
||||
Tty: ctx.Bool("tty"),
|
||||
User: ctx.String("user"),
|
||||
Uts: ctx.String("uts"),
|
||||
VolumeDriver: ctx.String("volume-driver"),
|
||||
WorkingDir: ctx.String("workdir"),
|
||||
DataVolumes: ctx.StringSlice("volume"),
|
||||
//todo: add RunInit
|
||||
//RunInit: ctx.Bool("init"),
|
||||
SecurityOpt: ctx.StringSlice("security-opt"),
|
||||
ShmSize: ctx.Int64("shm-size"),
|
||||
StdinOpen: ctx.Bool("interactive"),
|
||||
StopSignal: ctx.String("stop-signal"),
|
||||
Tty: ctx.Bool("tty"),
|
||||
User: ctx.String("user"),
|
||||
Uts: ctx.String("uts"),
|
||||
VolumeDriver: ctx.String("volume-driver"),
|
||||
WorkingDir: ctx.String("workdir"),
|
||||
DataVolumes: ctx.StringSlice("volume"),
|
||||
}
|
||||
if ctx.IsSet("it") {
|
||||
container.StdinOpen = true
|
||||
container.Tty = true
|
||||
}
|
||||
|
||||
if ctx.String("log-driver") != "" || len(ctx.StringSlice("log-opt")) > 0 {
|
||||
launchConfig.LogConfig = &client.LogConfig{
|
||||
container.LogConfig = &client.LogConfig{
|
||||
Driver: ctx.String("log-driver"),
|
||||
Config: map[string]interface{}{},
|
||||
Config: map[string]string{},
|
||||
}
|
||||
for _, opt := range ctx.StringSlice("log-opt") {
|
||||
parts := strings.SplitN(opt, "=", 2)
|
||||
if len(parts) > 1 {
|
||||
launchConfig.LogConfig.Config[parts[0]] = parts[1]
|
||||
container.LogConfig.Config[parts[0]] = parts[1]
|
||||
} else {
|
||||
launchConfig.LogConfig.Config[parts[0]] = ""
|
||||
container.LogConfig.Config[parts[0]] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -374,7 +523,7 @@ func serviceRun(ctx *cli.Context) error {
|
||||
if len(parts) > 1 {
|
||||
value = parts[1]
|
||||
}
|
||||
launchConfig.Labels[parts[0]] = value
|
||||
container.Labels[parts[0]] = value
|
||||
}
|
||||
|
||||
for _, env := range ctx.StringSlice("env") {
|
||||
@@ -391,40 +540,27 @@ func serviceRun(ctx *cli.Context) error {
|
||||
} else if len(parts) == 1 {
|
||||
value = os.Getenv(parts[0])
|
||||
}
|
||||
launchConfig.Environment[parts[0]] = value
|
||||
}
|
||||
|
||||
if ctx.Bool("schedule-global") {
|
||||
launchConfig.Labels["io.rancher.scheduler.global"] = "true"
|
||||
container.Environment[parts[0]] = value
|
||||
}
|
||||
|
||||
if ctx.Bool("pull") {
|
||||
launchConfig.Labels["io.rancher.container.pull_image"] = "always"
|
||||
container.Labels["io.rancher.container.pull_image"] = "always"
|
||||
}
|
||||
|
||||
args := ctx.Args()[1:]
|
||||
|
||||
if len(args) > 0 {
|
||||
launchConfig.Command = args
|
||||
container.Command = args
|
||||
}
|
||||
|
||||
stack, name, err := ParseName(c, ctx.String("name"))
|
||||
_, name, err := ParseName(c, ctx.String("name"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service := &client.Service{
|
||||
Name: name,
|
||||
StackId: stack.Id,
|
||||
LaunchConfig: launchConfig,
|
||||
StartOnCreate: true,
|
||||
Scale: int64(ctx.Int("scale")),
|
||||
}
|
||||
|
||||
service, err = c.Service.Create(service)
|
||||
container.Name = name
|
||||
cont, err := c.Container.Create(container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return WaitFor(ctx, service.Id)
|
||||
return WaitFor(ctx, cont.Id)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
||||
60
cmd/ssh.go
60
cmd/ssh.go
@@ -1,9 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@@ -12,7 +10,10 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"archive/zip"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@@ -20,7 +21,7 @@ func SSHCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "ssh",
|
||||
Usage: "SSH into host",
|
||||
Description: "\nFor any hosts created through Rancher using docker-machine, you can SSH into the host. This is not supported for any custom hosts. If the host is not in the current $RANCHER_ENVIRONMENT, use `--env <envID>` or `--env <envName>` to select a different environment.\n\nExample:\n\t$ rancher ssh 1h1\n\t$ rancher --env 1a5 ssh 1h5\n",
|
||||
Description: "\nFor any hosts created through Rancher using docker-machine, you can SSH into the host. This is not supported for any custom hosts. If the host is not in the current $RANCHER_ENVIRONMENT, use `--env <envID>` or `--env <envName>` to select a different environment.\n\nExample:\n\t$ rancher ssh root@1h1\n\t$ rancher --env 1a5 ssh ubuntu@1h5\n",
|
||||
ArgsUsage: "[HOSTID HOSTNAME...]",
|
||||
Action: hostSSH,
|
||||
Flags: []cli.Flag{},
|
||||
@@ -68,6 +69,8 @@ func hostSSH(ctx *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
user := getDefaultSSHKey(*host)
|
||||
|
||||
key, err := getSSHKey(hostname, *host, config.AccessKey, config.SecretKey)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -77,15 +80,22 @@ func hostSSH(ctx *cli.Context) error {
|
||||
return fmt.Errorf("Failed to find IP for %s", hostname)
|
||||
}
|
||||
|
||||
return processExitCode(callSSH(key, host.AgentIpAddress, ctx.Args()))
|
||||
return processExitCode(callSSH(key, host.AgentIpAddress, ctx.Args(), user))
|
||||
}
|
||||
|
||||
func callSSH(content []byte, ip string, args []string) error {
|
||||
func callSSH(content []byte, ip string, args []string, user string) error {
|
||||
for i, val := range args {
|
||||
if !strings.HasPrefix(val, "-") && len(val) > 0 {
|
||||
parts := strings.SplitN(val, "@", 2)
|
||||
parts[len(parts)-1] = ip
|
||||
args[i] = strings.Join(parts, "@")
|
||||
if len(parts) == 2 {
|
||||
parts[len(parts)-1] = ip
|
||||
args[i] = strings.Join(parts, "@")
|
||||
} else if len(parts) == 1 {
|
||||
if user == "" {
|
||||
return errors.New("Need to provide a ssh username")
|
||||
}
|
||||
args[i] = fmt.Sprintf("%s@%s", user, ip)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -127,6 +137,7 @@ func getSSHKey(hostname string, host client.Host, accessKey, secretKey string) (
|
||||
return nil, err
|
||||
}
|
||||
req.SetBasicAuth(accessKey, secretKey)
|
||||
req.Header.Add("Accept-Encoding", "zip")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
@@ -143,20 +154,33 @@ func getSSHKey(hostname string, host client.Host, accessKey, secretKey string) (
|
||||
return nil, fmt.Errorf("%s", tarGz)
|
||||
}
|
||||
|
||||
gzipIn, err := gzip.NewReader(bytes.NewBuffer(tarGz))
|
||||
zipReader, err := zip.NewReader(bytes.NewReader(tarGz), resp.ContentLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tar := tar.NewReader(gzipIn)
|
||||
|
||||
for {
|
||||
header, err := tar.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if path.Base(header.Name) == "id_rsa" {
|
||||
return ioutil.ReadAll(tar)
|
||||
for _, file := range zipReader.File {
|
||||
if path.Base(file.Name) == "id_rsa" {
|
||||
r, err := file.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Close()
|
||||
return ioutil.ReadAll(r)
|
||||
}
|
||||
}
|
||||
return nil, errors.New("can't find private key file")
|
||||
}
|
||||
|
||||
func getDefaultSSHKey(host client.Host) string {
|
||||
if host.Amazonec2Config != nil {
|
||||
return host.Amazonec2Config.SshUser
|
||||
}
|
||||
if host.DigitaloceanConfig != nil {
|
||||
return host.DigitaloceanConfig.SshUser
|
||||
}
|
||||
if host.Amazonec2Config != nil {
|
||||
return host.Amazonec2Config.SshUser
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
29
cmd/stack.go
29
cmd/stack.go
@@ -1,13 +1,11 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/rancher/rancher-compose-executor/lookup"
|
||||
"github.com/urfave/cli"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
func StackCommand() cli.Command {
|
||||
@@ -107,7 +105,6 @@ func stackLs(ctx *cli.Context) error {
|
||||
{"STATE", "State"},
|
||||
{"CATALOG", "Catalog"},
|
||||
{"SERVICES", "ServiceCount"},
|
||||
{"SYSTEM", "Stack.System"},
|
||||
{"DETAIL", "Stack.TransitioningMessage"},
|
||||
}, ctx)
|
||||
|
||||
@@ -164,26 +161,12 @@ func stackCreate(ctx *cli.Context) error {
|
||||
var lastErr error
|
||||
for _, name := range names {
|
||||
stack := &client.Stack{
|
||||
Name: name,
|
||||
System: ctx.Bool("system"),
|
||||
StartOnCreate: ctx.Bool("start"),
|
||||
Name: name,
|
||||
}
|
||||
|
||||
if !ctx.Bool("empty") {
|
||||
var err error
|
||||
stack.DockerCompose, err = getFile(ctx.String("docker-compose"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stack.DockerCompose == "" {
|
||||
return errors.New("docker-compose.yml files is required")
|
||||
}
|
||||
|
||||
stack.RancherCompose, err = getFile(ctx.String("rancher-compose"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "reading "+ctx.String("rancher-compose"))
|
||||
}
|
||||
|
||||
//var err error
|
||||
// todo: revisit
|
||||
//stack.Answers, err = parseAnswers(ctx)
|
||||
//if err != nil {
|
||||
//return errors.Wrap(err, "reading answers")
|
||||
|
||||
@@ -3,7 +3,7 @@ package cmd
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package cmd
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
||||
417
cmd/up.go
417
cmd/up.go
@@ -1,76 +1,379 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/rancher/rancher-compose-executor/app"
|
||||
"github.com/rancher/rancher-compose-executor/project"
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api/types"
|
||||
dclient "github.com/docker/docker/client"
|
||||
"github.com/fatih/color"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rancher/cli/monitor"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/rancher/rancher-docker-api-proxy"
|
||||
"github.com/urfave/cli"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var colors = []color.Attribute{color.FgGreen, color.FgBlack, color.FgBlue, color.FgCyan, color.FgMagenta, color.FgRed, color.FgWhite, color.FgYellow}
|
||||
|
||||
func UpCommand() cli.Command {
|
||||
factory := &projectFactory{}
|
||||
cmd := app.UpCommand(factory)
|
||||
cmd.Flags = append(cmd.Flags, []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "rancher-file",
|
||||
Usage: "Specify an alternate Rancher compose file (default: rancher-compose.yml)",
|
||||
return cli.Command{
|
||||
Name: "up",
|
||||
Usage: "Bring all services up",
|
||||
Action: rancherUp,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "pull, p",
|
||||
Usage: "Before doing the upgrade do an image pull on all hosts that have the image already",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "d",
|
||||
Usage: "Do not block and log",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "render",
|
||||
Usage: "Display processed Compose files and exit",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "upgrade, u, recreate",
|
||||
Usage: "Upgrade if service has changed",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "force-upgrade, force-recreate",
|
||||
Usage: "Upgrade regardless if service has changed",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "confirm-upgrade, c",
|
||||
Usage: "Confirm that the upgrade was success and delete old containers",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "rollback, r",
|
||||
Usage: "Rollback to the previous deployed version",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "batch-size",
|
||||
Usage: "Number of containers to upgrade at once",
|
||||
Value: 2,
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "interval",
|
||||
Usage: "Update interval in milliseconds",
|
||||
Value: 1000,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "rancher-file",
|
||||
Usage: "Specify an alternate Rancher compose file (default: rancher-compose.yml)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "env-file,e",
|
||||
Usage: "Specify a file from which to read environment variables",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "file,f",
|
||||
Usage: "Specify one or more alternate compose files (default: docker-compose.yml)",
|
||||
Value: &cli.StringSlice{},
|
||||
EnvVar: "COMPOSE_FILE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "stack,s",
|
||||
Usage: "Specify an alternate project name (default: directory name)",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "prune",
|
||||
Usage: "Prune services that doesn't exist on the current compose files",
|
||||
},
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "env-file,e",
|
||||
Usage: "Specify a file from which to read environment variables",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "file,f",
|
||||
Usage: "Specify one or more alternate compose files (default: docker-compose.yml)",
|
||||
Value: &cli.StringSlice{},
|
||||
EnvVar: "COMPOSE_FILE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "stack,s",
|
||||
Usage: "Specify an alternate project name (default: directory name)",
|
||||
},
|
||||
}...)
|
||||
|
||||
cmd.Action = app.WithProject(factory, ProjectUp)
|
||||
|
||||
return cmd
|
||||
}
|
||||
}
|
||||
|
||||
func ProjectUp(p *project.Project, c *cli.Context) error {
|
||||
w, err := NewWaiter(c)
|
||||
func rancherUp(ctx *cli.Context) error {
|
||||
rancherClient, err := GetClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// only look for --file or ./compose.yml
|
||||
compose := ""
|
||||
|
||||
composeFile := ctx.String("file")
|
||||
if composeFile != "" {
|
||||
composeFile = "compose.yml"
|
||||
}
|
||||
fp, err := filepath.Abs(composeFile)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to lookup current directory name")
|
||||
}
|
||||
file, err := os.Open(fp)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Can not find compose.yml")
|
||||
}
|
||||
defer file.Close()
|
||||
buf, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read file")
|
||||
}
|
||||
compose = string(buf)
|
||||
|
||||
//get stack name
|
||||
stackName := ""
|
||||
|
||||
if ctx.String("stack") != "" {
|
||||
stackName = ctx.String("stack")
|
||||
} else {
|
||||
parent := path.Base(path.Dir(fp))
|
||||
if parent != "" && parent != "." {
|
||||
stackName = parent
|
||||
} else if wd, err := os.Getwd(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
stackName = path.Base(toUnixPath(wd))
|
||||
}
|
||||
}
|
||||
|
||||
stacks, err := rancherClient.Stack.List(&client.ListOpts{
|
||||
Filters: map[string]interface{}{
|
||||
"name": stackName,
|
||||
"removed_null": nil,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to list stacks")
|
||||
}
|
||||
|
||||
if ctx.Bool("rollback") {
|
||||
if len(stacks.Data) == 0 {
|
||||
return errors.Errorf("Can't find stack %v", stackName)
|
||||
}
|
||||
_, err := rancherClient.Stack.ActionRollback(&stacks.Data[0])
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to rollback stack %v", stackName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !ctx.Bool("d") {
|
||||
watcher := monitor.NewUpWatcher(rancherClient)
|
||||
watcher.Subscribe()
|
||||
go func() { watcher.Start(stackName) }()
|
||||
}
|
||||
|
||||
if len(stacks.Data) > 0 {
|
||||
// update stacks
|
||||
stacks.Data[0].Templates = map[string]string{
|
||||
"compose.yml": compose,
|
||||
}
|
||||
prune := ctx.Bool("prune")
|
||||
logrus.Info("Updating stack")
|
||||
_, err := rancherClient.Stack.Update(&stacks.Data[0], client.Stack{
|
||||
Templates: map[string]string{
|
||||
"compose.yml": compose,
|
||||
},
|
||||
Prune: prune,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to update stack %v", stackName)
|
||||
}
|
||||
} else {
|
||||
// create new stack
|
||||
prune := ctx.Bool("prune")
|
||||
_, err := rancherClient.Stack.Create(&client.Stack{
|
||||
Name: stackName,
|
||||
Templates: map[string]string{
|
||||
"compose.yml": compose,
|
||||
},
|
||||
Prune: prune,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to create stack %v", stackName)
|
||||
}
|
||||
}
|
||||
|
||||
if !ctx.Bool("d") {
|
||||
for {
|
||||
stack, err := getStack(rancherClient, stackName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(stack.ServiceIds) != 0 {
|
||||
instanceIds := map[string]struct{}{}
|
||||
services, err := getServices(rancherClient, stack.ServiceIds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, service := range services {
|
||||
if service.Transitioning != "no" {
|
||||
logrus.Debugf("Service [%v] is not fully up", service.Name)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
for _, instanceID := range service.InstanceIds {
|
||||
instanceIds[instanceID] = struct{}{}
|
||||
}
|
||||
}
|
||||
if err := getLogs(rancherClient, instanceIds); err != nil {
|
||||
return errors.Wrapf(err, "failed to get container logs")
|
||||
}
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func toUnixPath(p string) string {
|
||||
return strings.Replace(p, "\\", "/", -1)
|
||||
}
|
||||
|
||||
func getStack(c *client.RancherClient, stackName string) (client.Stack, error) {
|
||||
stacks, err := c.Stack.List(&client.ListOpts{
|
||||
Filters: map[string]interface{}{
|
||||
"name": stackName,
|
||||
"removed_null": nil,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return client.Stack{}, errors.Wrap(err, "failed to list stacks")
|
||||
}
|
||||
if len(stacks.Data) > 0 {
|
||||
return stacks.Data[0], nil
|
||||
}
|
||||
return client.Stack{}, errors.Errorf("Failed to find stacks with name %v", stackName)
|
||||
}
|
||||
|
||||
func getServices(c *client.RancherClient, serviceIds []string) ([]client.Service, error) {
|
||||
services := []client.Service{}
|
||||
for _, serviceID := range serviceIds {
|
||||
service, err := c.Service.ById(serviceID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get service (id: [%v])", serviceID)
|
||||
}
|
||||
services = append(services, *service)
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
func getLogs(c *client.RancherClient, instanceIds map[string]struct{}) error {
|
||||
wg := sync.WaitGroup{}
|
||||
instances := []client.Instance{}
|
||||
for instanceID := range instanceIds {
|
||||
instance, err := c.Instance.ById(instanceID)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get instance id [%v]", instanceID)
|
||||
}
|
||||
instances = append(instances, *instance)
|
||||
}
|
||||
listenSocks := map[string]*dclient.Client{}
|
||||
for _, i := range instances {
|
||||
if i.ExternalId == "" || i.HostId == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if dockerClient, ok := listenSocks[i.HostId]; ok {
|
||||
wg.Add(1)
|
||||
go func(dockerClient *dclient.Client, i client.Instance) {
|
||||
if err := log(i, dockerClient); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
wg.Done()
|
||||
}(dockerClient, i)
|
||||
continue
|
||||
}
|
||||
|
||||
resource, err := Lookup(c, i.HostId, "host")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
host, err := c.Host.ById(resource.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
state := getHostState(host)
|
||||
if state != "active" && state != "inactive" {
|
||||
logrus.Errorf("Can not contact host %s in state %s", i.HostId, state)
|
||||
continue
|
||||
}
|
||||
|
||||
tempfile, err := ioutil.TempFile("", "docker-sock")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tempfile.Name())
|
||||
|
||||
if err := tempfile.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dockerHost := "unix://" + tempfile.Name()
|
||||
proxy := dockerapiproxy.NewProxy(c, host.Id, dockerHost)
|
||||
if err := proxy.Listen(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
logrus.Fatal(proxy.Serve())
|
||||
}()
|
||||
|
||||
dockerClient, err := dclient.NewClient(dockerHost, "", nil, nil)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to connect to host %s: %v", i.HostId, err)
|
||||
continue
|
||||
}
|
||||
|
||||
listenSocks[i.HostId] = dockerClient
|
||||
|
||||
wg.Add(1)
|
||||
go func(dockerClient *dclient.Client, i client.Instance) {
|
||||
if err := log(i, dockerClient); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
wg.Done()
|
||||
}(dockerClient, i)
|
||||
}
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func log(instance client.Instance, dockerClient *dclient.Client) error {
|
||||
c, err := dockerClient.ContainerInspect(context.Background(), instance.ExternalId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.ProjectUpAndWait(p, w, c)
|
||||
}
|
||||
|
||||
type projectFactory struct {
|
||||
}
|
||||
|
||||
func (p *projectFactory) Create(c *cli.Context) (*project.Project, error) {
|
||||
config, err := lookupConfig(c)
|
||||
options := types.ContainerLogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
Follow: true,
|
||||
Tail: "10",
|
||||
}
|
||||
responseBody, err := dockerClient.ContainerLogs(context.Background(), c.ID, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
defer responseBody.Close()
|
||||
|
||||
url, err := config.EnvironmentURL()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
scanner := bufio.NewScanner(responseBody)
|
||||
cl := getRandomColor()
|
||||
for scanner.Scan() {
|
||||
text := fmt.Sprintf("[%v]: %v\n", instance.Name, scanner.Text())
|
||||
color.New(cl).Fprint(os.Stdout, text)
|
||||
}
|
||||
|
||||
// from config
|
||||
c.GlobalSet("url", url)
|
||||
c.GlobalSet("access-key", config.AccessKey)
|
||||
c.GlobalSet("secret-key", config.SecretKey)
|
||||
|
||||
// copy from flags
|
||||
c.GlobalSet("rancher-file", c.String("rancher-file"))
|
||||
c.GlobalSet("env-file", c.String("env-file"))
|
||||
c.GlobalSet("project-name", c.String("stack"))
|
||||
for _, f := range c.StringSlice("file") {
|
||||
c.GlobalSet("file", f)
|
||||
}
|
||||
|
||||
factory := &app.RancherProjectFactory{}
|
||||
return factory.Create(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRandomColor() color.Attribute {
|
||||
s1 := rand.NewSource(time.Now().UnixNano())
|
||||
r1 := rand.New(s1)
|
||||
index := r1.Intn(8)
|
||||
return colors[index]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
)
|
||||
|
||||
func pickAction(resource *client.Resource, actions ...string) (string, error) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@@ -23,6 +23,7 @@ func forEachResourceWithClient(c *client.RancherClient, ctx *cli.Context, types
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
fmt.Println(ctx.Args())
|
||||
for _, id := range ctx.Args() {
|
||||
resource, err := Lookup(c, id, types...)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@@ -135,7 +135,7 @@ func volumeCreate(ctx *cli.Context) error {
|
||||
newVol := &client.Volume{
|
||||
Name: ctx.Args()[0],
|
||||
Driver: ctx.String("driver"),
|
||||
DriverOpts: map[string]interface{}{},
|
||||
DriverOpts: map[string]string{},
|
||||
}
|
||||
|
||||
for _, arg := range ctx.StringSlice("opt") {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/rancher/cli/monitor"
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
"github.com/rancher/rancher-compose-executor/project/options"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
45
main.go
45
main.go
@@ -3,8 +3,13 @@ package main
|
||||
import (
|
||||
"os"
|
||||
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rancher/cli/cmd"
|
||||
"github.com/rancher/cli/rancher_prompt"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@@ -132,6 +137,8 @@ func mainErr() error {
|
||||
cmd.HostCommand(),
|
||||
cmd.LogsCommand(),
|
||||
cmd.PsCommand(),
|
||||
cmd.PullCommand(),
|
||||
cmd.PromptCommand(),
|
||||
cmd.RestartCommand(),
|
||||
cmd.RmCommand(),
|
||||
cmd.RunCommand(),
|
||||
@@ -146,6 +153,42 @@ func mainErr() error {
|
||||
cmd.InspectCommand(),
|
||||
cmd.WaitCommand(),
|
||||
}
|
||||
for _, com := range app.Commands {
|
||||
rancherPrompt.Commands[com.Name] = com
|
||||
rancherPrompt.Commands[com.ShortName] = com
|
||||
}
|
||||
rancherPrompt.Flags = app.Flags
|
||||
parsed, err := parseArgs(os.Args)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return app.Run(os.Args)
|
||||
return app.Run(parsed)
|
||||
}
|
||||
|
||||
var singleAlphaLetterRegxp = regexp.MustCompile("[a-zA-Z]")
|
||||
|
||||
func parseArgs(args []string) ([]string, error) {
|
||||
result := []string{}
|
||||
for _, arg := range args {
|
||||
if strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") && len(arg) > 1 {
|
||||
for i, c := range arg[1:] {
|
||||
if string(c) == "=" {
|
||||
if i < 1 {
|
||||
return nil, errors.New("invalid input with '-' and '=' flag")
|
||||
}
|
||||
result[len(result)-1] = result[len(result)-1] + arg[i+1:]
|
||||
break
|
||||
} else if singleAlphaLetterRegxp.MatchString(string(c)) {
|
||||
result = append(result, "-"+string(c))
|
||||
} else {
|
||||
return nil, errors.Errorf("invalid input %v in flag", string(c))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = append(result, arg)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
63
main_test.go
Normal file
63
main_test.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gopkg.in/check.v1"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Hook up gocheck into the "go test" runner.
|
||||
func Test(t *testing.T) {
|
||||
check.TestingT(t)
|
||||
}
|
||||
|
||||
type MainTestSuite struct {
|
||||
}
|
||||
|
||||
var _ = check.Suite(&MainTestSuite{})
|
||||
|
||||
func (m *MainTestSuite) SetUpSuite(c *check.C) {
|
||||
}
|
||||
|
||||
func (m *MainTestSuite) TestParseArgs(c *check.C) {
|
||||
input := [][]string{
|
||||
{"rancher", "run", "--debug", "-itd"},
|
||||
{"rancher", "run", "--debug", "-itf=b"},
|
||||
{"rancher", "run", "--debug", "-itd#"},
|
||||
{"rancher", "run", "--debug", "-f=b"},
|
||||
{"rancher", "run", "--debug", "-=b"},
|
||||
{"rancher", "run", "--debug", "-"},
|
||||
}
|
||||
r0, err := parseArgs(input[0])
|
||||
if err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
c.Assert(r0, check.DeepEquals, []string{"rancher", "run", "--debug", "-i", "-t", "-d"})
|
||||
|
||||
r1, err := parseArgs(input[1])
|
||||
if err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
c.Assert(r1, check.DeepEquals, []string{"rancher", "run", "--debug", "-i", "-t", "-f=b"})
|
||||
|
||||
_, err = parseArgs(input[2])
|
||||
if err == nil {
|
||||
c.Fatal("should raise error")
|
||||
}
|
||||
|
||||
r3, err := parseArgs(input[3])
|
||||
if err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
c.Assert(r3, check.DeepEquals, []string{"rancher", "run", "--debug", "-f=b"})
|
||||
|
||||
_, err = parseArgs(input[4])
|
||||
if err == nil {
|
||||
c.Fatal("should raise error")
|
||||
}
|
||||
|
||||
r5, err := parseArgs(input[5])
|
||||
if err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
c.Assert(r5, check.DeepEquals, []string{"rancher", "run", "--debug", "-"})
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/rancher/go-rancher/v2"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
@@ -151,7 +151,7 @@ func (m *Monitor) watch(conn *websocket.Conn) error {
|
||||
continue
|
||||
}
|
||||
|
||||
logrus.Debugf("Event: %s %s %s", v.Name, v.ResourceType, v.ResourceID)
|
||||
logrus.Debugf("Event: %s %s %s %v", v.Name, v.ResourceType, v.ResourceID, v.Data)
|
||||
m.put(v.ResourceType, v.ResourceID, &v)
|
||||
}
|
||||
}
|
||||
|
||||
170
monitor/up_watcher.go
Normal file
170
monitor/up_watcher.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package monitor
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rancher/go-rancher/v3"
|
||||
)
|
||||
|
||||
type UpWatcher struct {
|
||||
sync.Mutex
|
||||
c *client.RancherClient
|
||||
subCounter int
|
||||
subscriptions map[int]*Subscription
|
||||
}
|
||||
|
||||
func (m *UpWatcher) Subscribe() *Subscription {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
m.subCounter++
|
||||
sub := &Subscription{
|
||||
id: m.subCounter,
|
||||
C: make(chan *Event, 1024),
|
||||
}
|
||||
m.subscriptions[sub.id] = sub
|
||||
|
||||
return sub
|
||||
}
|
||||
|
||||
func (m *UpWatcher) Unsubscribe(sub *Subscription) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
close(sub.C)
|
||||
delete(m.subscriptions, sub.id)
|
||||
}
|
||||
|
||||
func NewUpWatcher(c *client.RancherClient) *UpWatcher {
|
||||
return &UpWatcher{
|
||||
c: c,
|
||||
subscriptions: map[int]*Subscription{},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *UpWatcher) Start(stackName string) error {
|
||||
schema, ok := m.c.GetSchemas().CheckSchema("subscribe")
|
||||
if !ok {
|
||||
return fmt.Errorf("Not authorized to subscribe")
|
||||
}
|
||||
|
||||
urlString := schema.Links["collection"]
|
||||
u, err := url.Parse(urlString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "http":
|
||||
u.Scheme = "ws"
|
||||
case "https":
|
||||
u.Scheme = "wss"
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
q.Add("eventNames", "resource.change")
|
||||
q.Add("eventNames", "service.kubernetes.change")
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
conn, resp, err := m.c.Websocket(u.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 101 {
|
||||
return fmt.Errorf("Bad status code: %d %s", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
logrus.Debugf("Connected to: %s", u.String())
|
||||
|
||||
return m.watch(conn, stackName)
|
||||
}
|
||||
|
||||
func (m *UpWatcher) watch(conn *websocket.Conn, stackName string) error {
|
||||
stackID := ""
|
||||
serviceIds := map[string]struct{}{}
|
||||
lastStackMsg := ""
|
||||
lastServiceMsg := ""
|
||||
lastContainerMsg := ""
|
||||
for {
|
||||
v := Event{}
|
||||
_, r, err := conn.NextReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.NewDecoder(r).Decode(&v); err != nil {
|
||||
logrus.Errorf("Failed to parse json in message")
|
||||
continue
|
||||
}
|
||||
|
||||
logrus.Debugf("Event: %s %s %s", v.Name, v.ResourceType, v.ResourceID)
|
||||
if v.ResourceType == "stack" {
|
||||
stackData := &client.Stack{}
|
||||
if err := unmarshalling(v.Data["resource"], stackData); err != nil {
|
||||
logrus.Errorf("failed to unmarshalling err: %v", err)
|
||||
}
|
||||
if stackData.Name == stackName {
|
||||
stackID = stackData.Id
|
||||
for _, serviceID := range stackData.ServiceIds {
|
||||
serviceIds[serviceID] = struct{}{}
|
||||
}
|
||||
switch stackData.Transitioning {
|
||||
case "yes":
|
||||
msg := fmt.Sprintf("Stack [%v]: %s", stackData.Name, stackData.TransitioningMessage)
|
||||
if msg != lastStackMsg {
|
||||
logrus.Info(msg)
|
||||
}
|
||||
lastStackMsg = msg
|
||||
}
|
||||
}
|
||||
} else if v.ResourceType == "scalingGroup" {
|
||||
serviceData := &client.Service{}
|
||||
if err := unmarshalling(v.Data["resource"], serviceData); err != nil {
|
||||
logrus.Errorf("failed to unmarshalling err: %v", err)
|
||||
}
|
||||
if serviceData.StackId == stackID {
|
||||
switch serviceData.Transitioning {
|
||||
case "yes":
|
||||
msg := fmt.Sprintf("Service [%v]: %s", serviceData.Name, serviceData.TransitioningMessage)
|
||||
if msg != lastServiceMsg {
|
||||
logrus.Info(msg)
|
||||
}
|
||||
lastServiceMsg = msg
|
||||
}
|
||||
}
|
||||
} else if v.ResourceType == "container" {
|
||||
containerData := &client.Container{}
|
||||
if err := unmarshalling(v.Data["resource"], containerData); err != nil {
|
||||
logrus.Errorf("failed to unmarshalling err: %v", err)
|
||||
}
|
||||
if containerData.StackId == stackID {
|
||||
switch containerData.Transitioning {
|
||||
case "yes":
|
||||
msg := fmt.Sprintf("Container [%v]: %s", containerData.Name, containerData.TransitioningMessage)
|
||||
if msg != lastContainerMsg {
|
||||
logrus.Info(msg)
|
||||
}
|
||||
lastContainerMsg = msg
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalling(data interface{}, v interface{}) error {
|
||||
raw, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to marshall object. Body: %v", data)
|
||||
}
|
||||
if err := json.Unmarshal(raw, &v); err != nil {
|
||||
return errors.Wrapf(err, "failed to unmarshall object. Body: %v", string(raw))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
1
rancher_prompt/client.go
Normal file
1
rancher_prompt/client.go
Normal file
@@ -0,0 +1 @@
|
||||
package rancherPrompt
|
||||
120
rancher_prompt/completer.go
Normal file
120
rancher_prompt/completer.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package rancherPrompt
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/c-bata/go-prompt"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// thanks for the idea from github.com/c-bata/kube-prompt
|
||||
|
||||
var (
|
||||
Commands = map[string]cli.Command{}
|
||||
Flags = []cli.Flag{}
|
||||
)
|
||||
|
||||
func Completer(d prompt.Document) []prompt.Suggest {
|
||||
if d.TextBeforeCursor() == "" {
|
||||
return []prompt.Suggest{}
|
||||
}
|
||||
|
||||
args := strings.Split(d.TextBeforeCursor(), " ")
|
||||
w := d.GetWordBeforeCursor()
|
||||
|
||||
// If PIPE is in text before the cursor, returns empty suggestions.
|
||||
for i := range args {
|
||||
if args[i] == "|" {
|
||||
return []prompt.Suggest{}
|
||||
}
|
||||
}
|
||||
|
||||
// If word before the cursor starts with "-", returns CLI flag options.
|
||||
if strings.HasPrefix(w, "-") {
|
||||
return optionCompleter(args, strings.HasPrefix(w, "--"))
|
||||
}
|
||||
|
||||
return argumentsCompleter(excludeOptions(args))
|
||||
}
|
||||
|
||||
func argumentsCompleter(args []string) []prompt.Suggest {
|
||||
suggests := []prompt.Suggest{}
|
||||
for name, command := range Commands {
|
||||
if command.Name != "prompt" {
|
||||
suggests = append(suggests, prompt.Suggest{
|
||||
Text: name,
|
||||
Description: command.Usage,
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(args) <= 1 {
|
||||
return prompt.FilterHasPrefix(suggests, args[0], true)
|
||||
}
|
||||
|
||||
switch args[0] {
|
||||
case "docker":
|
||||
if len(args) == 3 {
|
||||
subcommands := []prompt.Suggest{
|
||||
{Text: "attach", Description: "Attach local standard input, output, and error streams to a running container"},
|
||||
{Text: "build", Description: "Build an image from a Dockerfile"},
|
||||
{Text: "commit", Description: "Create a new image from a container’s changes"},
|
||||
{Text: "cp", Description: "Copy files/folders between a container and the local filesystem"},
|
||||
{Text: "create", Description: "Create a new container"},
|
||||
{Text: "events", Description: "Get real time events from the server"},
|
||||
{Text: "exec", Description: "Run a command in a running container"},
|
||||
{Text: "export", Description: "Export a container’s filesystem as a tar archive"},
|
||||
{Text: "image", Description: "Manage images"},
|
||||
{Text: "images", Description: "List images"},
|
||||
{Text: "import", Description: "Import the contents from a tarball to create a filesystem image"},
|
||||
{Text: "info", Description: "Display system-wide information"},
|
||||
{Text: "inspect", Description: "Return low-level information on Docker objects"},
|
||||
{Text: "kill", Description: "Kill one or more running containers"},
|
||||
{Text: "load", Description: "Load an image from a tar archive or STDIN"},
|
||||
{Text: "login", Description: "Log in to a Docker registry"},
|
||||
{Text: "logout", Description: "Log out from a Docker registry"},
|
||||
{Text: "logs", Description: "Fetch the logs of a container"},
|
||||
{Text: "network", Description: "Manage networks"},
|
||||
{Text: "pause", Description: "Pause all processes within one or more containers"},
|
||||
{Text: "plugin", Description: "Manage plugins"},
|
||||
{Text: "port", Description: "List port mappings or a specific mapping for the container"},
|
||||
{Text: "ps", Description: "List containers"},
|
||||
{Text: "pull", Description: "Pull an image or a repository from a registry"},
|
||||
{Text: "push", Description: "Push an image or a repository to a registry"},
|
||||
{Text: "rename", Description: "Rename a container"},
|
||||
{Text: "restart", Description: "Restart one or more containers"},
|
||||
{Text: "rm", Description: "Remove one or more containers"},
|
||||
{Text: "rmi", Description: "Remove one or more images"},
|
||||
{Text: "run", Description: "Run a command in a new container"},
|
||||
{Text: "save", Description: "Save one or more images to a tar archive (streamed to STDOUT by default)"},
|
||||
{Text: "search", Description: "Search the Docker Hub for images"},
|
||||
{Text: "start", Description: "Start one or more stopped containers"},
|
||||
{Text: "stats", Description: "Display a live stream of container(s) resource usage statistics"},
|
||||
{Text: "stop", Description: "Stop one or more running containers"},
|
||||
{Text: "tag", Description: "Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE"},
|
||||
{Text: "top", Description: "Display the running processes of a container"},
|
||||
{Text: "unpause", Description: "Unpause all processes within one or more containers"},
|
||||
{Text: "update", Description: "Update configuration of one or more containers"},
|
||||
{Text: "version", Description: "Show the Docker version information"},
|
||||
{Text: "volume", Description: "Manage volumes"},
|
||||
{Text: "wait", Description: "Block until one or more containers stop, then print their exit codes"},
|
||||
}
|
||||
return prompt.FilterHasPrefix(subcommands, args[2], true)
|
||||
}
|
||||
default:
|
||||
if len(args) == 2 {
|
||||
return prompt.FilterHasPrefix(getSubcommandSuggest(args[0]), args[1], true)
|
||||
}
|
||||
}
|
||||
return []prompt.Suggest{}
|
||||
}
|
||||
|
||||
func getSubcommandSuggest(name string) []prompt.Suggest {
|
||||
subcommands := []prompt.Suggest{}
|
||||
for _, com := range Commands[name].Subcommands {
|
||||
subcommands = append(subcommands, prompt.Suggest{
|
||||
Text: com.Name,
|
||||
Description: com.Usage,
|
||||
})
|
||||
}
|
||||
return subcommands
|
||||
}
|
||||
40
rancher_prompt/executor.go
Normal file
40
rancher_prompt/executor.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package rancherPrompt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Executor(s string) {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return
|
||||
}
|
||||
if s == "exit" {
|
||||
os.Exit(0)
|
||||
return
|
||||
}
|
||||
//hack for rancher docker
|
||||
// docker --host 1h1 ps -> --host 1h1 docker ps
|
||||
if strings.HasPrefix(s, "docker ") {
|
||||
parts := strings.Split(s, " ")
|
||||
if len(parts) > 2 && (parts[1] == "--host" || parts[1] == "-host") {
|
||||
t := parts[0]
|
||||
parts[0] = parts[1]
|
||||
parts[1] = parts[2]
|
||||
parts[2] = t
|
||||
s = strings.Join(parts, " ")
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command("/bin/sh", "-c", "rancher "+s)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Printf("Got error: %s\n", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
117
rancher_prompt/options.go
Normal file
117
rancher_prompt/options.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package rancherPrompt
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/c-bata/go-prompt"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func optionCompleter(args []string, long bool) []prompt.Suggest {
|
||||
l := len(args)
|
||||
if l <= 1 {
|
||||
if long {
|
||||
return prompt.FilterHasPrefix(optionHelp, "--", false)
|
||||
}
|
||||
return optionHelp
|
||||
}
|
||||
flagGlobal := getGlobalFlag()
|
||||
|
||||
var suggests []prompt.Suggest
|
||||
commandArgs := excludeOptions(args)
|
||||
|
||||
if command, ok := Commands[commandArgs[0]]; ok {
|
||||
if len(commandArgs) > 1 && len(command.Subcommands) > 0 {
|
||||
for _, sub := range command.Subcommands {
|
||||
if sub.Name == commandArgs[1] {
|
||||
suggests = append(getFlagsSuggests(sub), flagGlobal...)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
suggests = append(getFlagsSuggests(command), flagGlobal...)
|
||||
}
|
||||
}
|
||||
|
||||
if long {
|
||||
return prompt.FilterContains(
|
||||
prompt.FilterHasPrefix(suggests, "--", false),
|
||||
strings.TrimLeft(args[l-1], "--"),
|
||||
true,
|
||||
)
|
||||
}
|
||||
return prompt.FilterHasPrefix(suggests, strings.TrimLeft(args[l-1], "-"), true)
|
||||
}
|
||||
|
||||
var optionHelp = []prompt.Suggest{
|
||||
{Text: "-h", Description: "Help Commmand"},
|
||||
{Text: "--help", Description: "Help Commmand"},
|
||||
}
|
||||
|
||||
func excludeOptions(args []string) []string {
|
||||
ret := make([]string, 0, len(args))
|
||||
for i := range args {
|
||||
if !strings.HasPrefix(args[i], "-") {
|
||||
ret = append(ret, args[i])
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func getGlobalFlag() []prompt.Suggest {
|
||||
suggests := []prompt.Suggest{}
|
||||
for _, flag := range Flags {
|
||||
name := flag.GetName()
|
||||
parts := strings.Split(name, ",")
|
||||
for _, part := range parts {
|
||||
prefix := "--"
|
||||
if len(parts) == 1 {
|
||||
prefix = "-"
|
||||
}
|
||||
suggests = append(suggests, prompt.Suggest{
|
||||
Text: prefix + strings.TrimSpace(part),
|
||||
Description: getUsageForFlag(flag),
|
||||
})
|
||||
}
|
||||
}
|
||||
suggests = append(suggests, optionHelp...)
|
||||
return suggests
|
||||
}
|
||||
|
||||
func getFlagsSuggests(command cli.Command) []prompt.Suggest {
|
||||
suggests := []prompt.Suggest{}
|
||||
for _, f := range command.Flags {
|
||||
name := f.GetName()
|
||||
parts := strings.Split(name, ",")
|
||||
for _, part := range parts {
|
||||
prefix := "--"
|
||||
if len(parts) == 1 {
|
||||
prefix = "-"
|
||||
}
|
||||
suggests = append(suggests, prompt.Suggest{
|
||||
Text: prefix + strings.TrimSpace(part),
|
||||
Description: getUsageForFlag(f),
|
||||
})
|
||||
}
|
||||
}
|
||||
return suggests
|
||||
}
|
||||
|
||||
func getUsageForFlag(flag cli.Flag) string {
|
||||
if v, ok := flag.(cli.StringFlag); ok {
|
||||
return v.Usage
|
||||
}
|
||||
if v, ok := flag.(cli.StringSliceFlag); ok {
|
||||
return v.Usage
|
||||
}
|
||||
if v, ok := flag.(cli.IntFlag); ok {
|
||||
return v.Usage
|
||||
}
|
||||
if v, ok := flag.(cli.IntSliceFlag); ok {
|
||||
return v.Usage
|
||||
}
|
||||
if v, ok := flag.(cli.BoolFlag); ok {
|
||||
return v.Usage
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user