mirror of
https://github.com/rancher/os.git
synced 2025-06-29 08:16:49 +00:00
Merge pull request #980 from joshwget/first-class-consoles
First class consoles
This commit is contained in:
commit
cd76d85aea
@ -31,6 +31,12 @@ func Main() {
|
||||
HideHelp: true,
|
||||
Subcommands: configSubcommands(),
|
||||
},
|
||||
{
|
||||
Name: "console",
|
||||
Usage: "console container commands",
|
||||
HideHelp: true,
|
||||
Subcommands: consoleSubcommands(),
|
||||
},
|
||||
{
|
||||
Name: "dev",
|
||||
ShortName: "d",
|
||||
|
104
cmd/control/console.go
Normal file
104
cmd/control/console.go
Normal file
@ -0,0 +1,104 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/cli"
|
||||
composeConfig "github.com/docker/libcompose/config"
|
||||
"github.com/docker/libcompose/project/options"
|
||||
"github.com/rancher/os/compose"
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/docker"
|
||||
"github.com/rancher/os/util"
|
||||
"github.com/rancher/os/util/network"
|
||||
)
|
||||
|
||||
func consoleSubcommands() []cli.Command {
|
||||
return []cli.Command{
|
||||
{
|
||||
Name: "switch",
|
||||
Usage: "switch currently running console",
|
||||
Action: consoleSwitch,
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "list available consoles",
|
||||
Action: consoleList,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func consoleSwitch(c *cli.Context) error {
|
||||
if len(c.Args()) != 1 {
|
||||
log.Fatal("Must specify exactly one existing container")
|
||||
}
|
||||
newConsole := c.Args()[0]
|
||||
|
||||
in := bufio.NewReader(os.Stdin)
|
||||
question := fmt.Sprintf("Switching consoles will destroy the current console container and restart Docker. Continue")
|
||||
if !yes(in, question) {
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := config.LoadConfig()
|
||||
|
||||
if err := compose.StageServices(cfg, newConsole); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := docker.NewSystemClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentContainerId, err := util.GetCurrentContainerId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentContainer, err := client.ContainerInspect(context.Background(), currentContainerId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service, err := compose.CreateService(nil, "switch-console", &composeConfig.ServiceConfigV1{
|
||||
LogDriver: "json-file",
|
||||
Privileged: true,
|
||||
Net: "host",
|
||||
Pid: "host",
|
||||
Image: currentContainer.Config.Image,
|
||||
Labels: map[string]string{
|
||||
config.SCOPE: config.SYSTEM,
|
||||
},
|
||||
Command: []string{"/usr/bin/switch-console", newConsole},
|
||||
VolumesFrom: []string{"all-volumes"},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = service.Delete(context.Background(), options.Delete{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return service.Up(context.Background(), options.Up{})
|
||||
}
|
||||
|
||||
func consoleList(c *cli.Context) error {
|
||||
cfg := config.LoadConfig()
|
||||
|
||||
consoles, err := network.GetConsoles(cfg.Rancher.Repositories.ToArray())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, console := range consoles {
|
||||
fmt.Println(console)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -177,16 +177,6 @@ func osVersion(c *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func yes(in *bufio.Reader, question string) bool {
|
||||
fmt.Printf("%s [y/N]: ", question)
|
||||
line, err := in.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return strings.ToLower(line[0:1]) == "y"
|
||||
}
|
||||
|
||||
func startUpgradeContainer(image string, stage, force, reboot, kexec bool, upgradeConsole bool, kernelArgs string) error {
|
||||
in := bufio.NewReader(os.Stdin)
|
||||
|
||||
|
19
cmd/control/util.go
Normal file
19
cmd/control/util.go
Normal file
@ -0,0 +1,19 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
func yes(in *bufio.Reader, question string) bool {
|
||||
fmt.Printf("%s [y/N]: ", question)
|
||||
line, err := in.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return strings.ToLower(line[0:1]) == "y"
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package power
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -17,10 +16,7 @@ import (
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
|
||||
"github.com/rancher/os/docker"
|
||||
)
|
||||
|
||||
const (
|
||||
DOCKER_CGROUPS_FILE = "/proc/self/cgroup"
|
||||
"github.com/rancher/os/util"
|
||||
)
|
||||
|
||||
func runDocker(name string) error {
|
||||
@ -51,7 +47,7 @@ func runDocker(name string) error {
|
||||
}
|
||||
}
|
||||
|
||||
currentContainerId, err := getCurrentContainerId()
|
||||
currentContainerId, err := util.GetCurrentContainerId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -185,7 +181,7 @@ func shutDownContainers() error {
|
||||
return err
|
||||
}
|
||||
|
||||
currentContainerId, err := getCurrentContainerId()
|
||||
currentContainerId, err := util.GetCurrentContainerId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -222,35 +218,3 @@ func shutDownContainers() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCurrentContainerId() (string, error) {
|
||||
file, err := os.Open(DOCKER_CGROUPS_FILE)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fileReader := bufio.NewScanner(file)
|
||||
if !fileReader.Scan() {
|
||||
return "", errors.New("Empty file /proc/self/cgroup")
|
||||
}
|
||||
line := fileReader.Text()
|
||||
parts := strings.Split(line, "/")
|
||||
|
||||
for len(parts) != 3 {
|
||||
if !fileReader.Scan() {
|
||||
return "", errors.New("Found no docker cgroups")
|
||||
}
|
||||
line = fileReader.Text()
|
||||
parts = strings.Split(line, "/")
|
||||
if len(parts) == 3 {
|
||||
if strings.HasSuffix(parts[1], "docker") {
|
||||
break
|
||||
} else {
|
||||
parts = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parts[len(parts)-1:][0], nil
|
||||
}
|
||||
|
41
cmd/switchconsole/switch_console.go
Normal file
41
cmd/switchconsole/switch_console.go
Normal file
@ -0,0 +1,41 @@
|
||||
package switchconsole
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/libcompose/project/options"
|
||||
"github.com/rancher/os/compose"
|
||||
"github.com/rancher/os/config"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func Main() {
|
||||
if len(os.Args) != 2 {
|
||||
log.Fatal("Must specify exactly one existing container")
|
||||
}
|
||||
newConsole := os.Args[1]
|
||||
|
||||
cfg := config.LoadConfig()
|
||||
|
||||
project, err := compose.GetProject(cfg, true)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err = compose.LoadService(project, cfg, true, newConsole); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err = project.Up(context.Background(), options.Up{}, "console"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err = project.Restart(context.Background(), 10, "docker"); err != nil {
|
||||
log.Errorf("Failed to restart Docker: %v", err)
|
||||
}
|
||||
|
||||
if err = config.Set("rancher.console", newConsole); err != nil {
|
||||
log.Errorf("Failed to update 'rancher.console': %v", err)
|
||||
}
|
||||
}
|
@ -183,9 +183,6 @@ func adjustContainerNames(m map[interface{}]interface{}) map[interface{}]interfa
|
||||
}
|
||||
|
||||
func newCoreServiceProject(cfg *config.CloudConfig, useNetwork bool) (*project.Project, error) {
|
||||
projectEvents := make(chan events.Event)
|
||||
enabled := map[interface{}]interface{}{}
|
||||
|
||||
environmentLookup := rosDocker.NewConfigEnvironment(cfg)
|
||||
authLookup := rosDocker.NewConfigAuthLookup(cfg)
|
||||
|
||||
@ -194,53 +191,11 @@ func newCoreServiceProject(cfg *config.CloudConfig, useNetwork bool) (*project.P
|
||||
return nil, err
|
||||
}
|
||||
|
||||
projectEvents := make(chan events.Event)
|
||||
p.AddListener(project.NewDefaultListener(p))
|
||||
p.AddListener(projectEvents)
|
||||
|
||||
p.ReloadCallback = func() error {
|
||||
cfg = config.LoadConfig()
|
||||
|
||||
environmentLookup.SetConfig(cfg)
|
||||
authLookup.SetConfig(cfg)
|
||||
|
||||
enabled = addServices(p, enabled, cfg.Rancher.Services)
|
||||
|
||||
for service, serviceEnabled := range cfg.Rancher.ServicesInclude {
|
||||
if _, ok := enabled[service]; ok || !serviceEnabled {
|
||||
continue
|
||||
}
|
||||
|
||||
bytes, err := network.LoadServiceResource(service, useNetwork, cfg)
|
||||
if err != nil {
|
||||
if err == network.ErrNoNetwork {
|
||||
log.Debugf("Can not load %s, networking not enabled", service)
|
||||
} else {
|
||||
log.Errorf("Failed to load %s : %v", service, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
m := map[interface{}]interface{}{}
|
||||
if err := yaml.Unmarshal(bytes, &m); err != nil {
|
||||
log.Errorf("Failed to parse YAML configuration: %s : %v", service, err)
|
||||
continue
|
||||
}
|
||||
bytes, err = yaml.Marshal(adjustContainerNames(m))
|
||||
if err != nil {
|
||||
log.Errorf("Failed to marshal YAML configuration: %s : %v", service, err)
|
||||
continue
|
||||
}
|
||||
err = p.Load(bytes)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to load %s : %v", service, err)
|
||||
continue
|
||||
}
|
||||
|
||||
enabled[service] = service
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
p.ReloadCallback = projectReload(p, &useNetwork, environmentLookup, authLookup)
|
||||
|
||||
go func() {
|
||||
for event := range projectEvents {
|
||||
|
73
compose/reload.go
Normal file
73
compose/reload.go
Normal file
@ -0,0 +1,73 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
||||
"github.com/docker/libcompose/project"
|
||||
"github.com/rancher/os/config"
|
||||
"github.com/rancher/os/docker"
|
||||
"github.com/rancher/os/util/network"
|
||||
)
|
||||
|
||||
func LoadService(p *project.Project, cfg *config.CloudConfig, useNetwork bool, service string) error {
|
||||
bytes, err := network.LoadServiceResource(service, useNetwork, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m := map[interface{}]interface{}{}
|
||||
if err = yaml.Unmarshal(bytes, &m); err != nil {
|
||||
return fmt.Errorf("Failed to parse YAML configuration for %s: %v", service, err)
|
||||
}
|
||||
|
||||
m = adjustContainerNames(m)
|
||||
|
||||
bytes, err = yaml.Marshal(m)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to marshal YAML configuration for %s: %v", service, err)
|
||||
}
|
||||
|
||||
if err = p.Load(bytes); err != nil {
|
||||
return fmt.Errorf("Failed to load %s: %v", service, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func projectReload(p *project.Project, useNetwork *bool, environmentLookup *docker.ConfigEnvironment, authLookup *docker.ConfigAuthLookup) func() error {
|
||||
enabled := map[interface{}]interface{}{}
|
||||
return func() error {
|
||||
cfg := config.LoadConfig()
|
||||
|
||||
environmentLookup.SetConfig(cfg)
|
||||
authLookup.SetConfig(cfg)
|
||||
|
||||
enabled = addServices(p, enabled, cfg.Rancher.Services)
|
||||
|
||||
for service, serviceEnabled := range cfg.Rancher.ServicesInclude {
|
||||
if _, ok := enabled[service]; ok || !serviceEnabled {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := LoadService(p, cfg, *useNetwork, service); err != nil {
|
||||
if err != network.ErrNoNetwork {
|
||||
log.Error(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
enabled[service] = service
|
||||
}
|
||||
|
||||
if cfg.Rancher.Console != "" {
|
||||
err := LoadService(p, cfg, *useNetwork, cfg.Rancher.Console)
|
||||
if err != nil && err != network.ErrNoNetwork {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
@ -85,6 +85,7 @@ type CloudConfig struct {
|
||||
}
|
||||
|
||||
type RancherConfig struct {
|
||||
Console string `yaml:"console,omitempty"`
|
||||
Environment map[string]string `yaml:"environment,omitempty"`
|
||||
Services map[string]*composeConfig.ServiceConfigV1 `yaml:"services,omitempty"`
|
||||
BootstrapContainers map[string]*composeConfig.ServiceConfigV1 `yaml:"bootstrap,omitempty"`
|
||||
|
2
main.go
2
main.go
@ -9,6 +9,7 @@ import (
|
||||
"github.com/rancher/os/cmd/network"
|
||||
"github.com/rancher/os/cmd/power"
|
||||
"github.com/rancher/os/cmd/respawn"
|
||||
"github.com/rancher/os/cmd/switchconsole"
|
||||
"github.com/rancher/os/cmd/sysinit"
|
||||
"github.com/rancher/os/cmd/systemdocker"
|
||||
"github.com/rancher/os/cmd/userdocker"
|
||||
@ -28,6 +29,7 @@ var entrypoints = map[string]func(){
|
||||
"respawn": respawn.Main,
|
||||
"ros-sysinit": sysinit.Main,
|
||||
"shutdown": power.Main,
|
||||
"switch-console": switchconsole.Main,
|
||||
"system-docker": systemdocker.Main,
|
||||
"user-docker": userdocker.Main,
|
||||
"wait-for-docker": wait.Main,
|
||||
|
@ -154,6 +154,7 @@ rancher:
|
||||
- /usr/bin/ros:/usr/bin/cloud-init:ro
|
||||
- /usr/bin/ros:/usr/sbin/netconf:ro
|
||||
- /usr/bin/ros:/usr/sbin/wait-for-docker:ro
|
||||
- /usr/bin/ros:/usr/bin/switch-console:ro
|
||||
console:
|
||||
image: {{.OS_REPO}}/os-console:{{.VERSION}}{{.SUFFIX}}
|
||||
labels:
|
||||
|
@ -1,6 +1,5 @@
|
||||
#cloud-config
|
||||
rancher:
|
||||
services_include:
|
||||
debian-console: true
|
||||
console: debian
|
||||
ssh_authorized_keys:
|
||||
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDUlsWAL5Rf0Wis/A7k7Tlqx0fZS60VzCZrPZYbP/wkL95jv0XzCx8bd1rZHeybblHPDNpND3BLv4qPY5DxRyexF4seGuzcJI/pOvGUGjQondeMPgDTFEo5w939gSdeTZcfXzQ0wAVhzwDbgH4zPfMzbdoo8Aiu9jkKljXw8IFju0gh+t6iKkGZCIjKT9o7zza1vGfkodhvi2V3VzPdNO28gaxZaRNtmBYUoVnGyR6nXN1Q3CJaVuh5o6GPCOqrhHNbYOFZKBpDiHbxPhVpxHQD2+8yUSGTG7WW75FfZePja5y8d0c/O5L37ZYx4AZAd3KgQYDBT2XCEJGQNawNbfpt
|
||||
|
@ -1,6 +1,5 @@
|
||||
#cloud-config
|
||||
rancher:
|
||||
services_include:
|
||||
debian-console: true
|
||||
console: debian
|
||||
ssh_authorized_keys:
|
||||
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDUlsWAL5Rf0Wis/A7k7Tlqx0fZS60VzCZrPZYbP/wkL95jv0XzCx8bd1rZHeybblHPDNpND3BLv4qPY5DxRyexF4seGuzcJI/pOvGUGjQondeMPgDTFEo5w939gSdeTZcfXzQ0wAVhzwDbgH4zPfMzbdoo8Aiu9jkKljXw8IFju0gh+t6iKkGZCIjKT9o7zza1vGfkodhvi2V3VzPdNO28gaxZaRNtmBYUoVnGyR6nXN1Q3CJaVuh5o6GPCOqrhHNbYOFZKBpDiHbxPhVpxHQD2+8yUSGTG7WW75FfZePja5y8d0c/O5L37ZYx4AZAd3KgQYDBT2XCEJGQNawNbfpt
|
||||
|
@ -1,11 +1,10 @@
|
||||
#cloud-config
|
||||
rancher:
|
||||
console: debian
|
||||
services:
|
||||
missing_image:
|
||||
image: tianon/true
|
||||
labels:
|
||||
io.rancher.os.scope: system
|
||||
services_include:
|
||||
debian-console: true
|
||||
ssh_authorized_keys:
|
||||
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC85w9stZyiLQp/DkVO6fqwiShYcj1ClKdtCqgHtf+PLpJkFReSFu8y21y+ev09gsSMRRrjF7yt0pUHV6zncQhVeqsZtgc5WbELY2DOYUGmRn/CCvPbXovoBrQjSorqlBmpuPwsStYLr92Xn+VVsMNSUIegHY22DphGbDKG85vrKB8HxUxGIDxFBds/uE8FhSy+xsoyT/jUZDK6pgq2HnGl6D81ViIlKecpOpWlW3B+fea99ADNyZNVvDzbHE5pcI3VRw8u59WmpWOUgT6qacNVACl8GqpBvQk8sw7O/X9DSZHCKafeD9G5k+GYbAUz92fKWrx/lOXfUXPS3+c8dRIF
|
||||
|
@ -21,6 +21,14 @@ var (
|
||||
)
|
||||
|
||||
func GetServices(urls []string) ([]string, error) {
|
||||
return getServices(urls, "services")
|
||||
}
|
||||
|
||||
func GetConsoles(urls []string) ([]string, error) {
|
||||
return getServices(urls, "consoles")
|
||||
}
|
||||
|
||||
func getServices(urls []string, key string) ([]string, error) {
|
||||
result := []string{}
|
||||
|
||||
for _, url := range urls {
|
||||
@ -38,7 +46,7 @@ func GetServices(urls []string) ([]string, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if list, ok := services["services"]; ok {
|
||||
if list, ok := services[key]; ok {
|
||||
result = append(result, list...)
|
||||
}
|
||||
}
|
||||
|
38
util/util.go
38
util/util.go
@ -1,7 +1,9 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@ -13,6 +15,10 @@ import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
DOCKER_CGROUPS_FILE = "/proc/self/cgroup"
|
||||
)
|
||||
|
||||
type AnyMap map[interface{}]interface{}
|
||||
|
||||
func Contains(values []string, value string) bool {
|
||||
@ -187,3 +193,35 @@ func TrimSplitN(str, sep string, count int) []string {
|
||||
func TrimSplit(str, sep string) []string {
|
||||
return TrimSplitN(str, sep, -1)
|
||||
}
|
||||
|
||||
func GetCurrentContainerId() (string, error) {
|
||||
file, err := os.Open(DOCKER_CGROUPS_FILE)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fileReader := bufio.NewScanner(file)
|
||||
if !fileReader.Scan() {
|
||||
return "", errors.New("Empty file /proc/self/cgroup")
|
||||
}
|
||||
line := fileReader.Text()
|
||||
parts := strings.Split(line, "/")
|
||||
|
||||
for len(parts) != 3 {
|
||||
if !fileReader.Scan() {
|
||||
return "", errors.New("Found no docker cgroups")
|
||||
}
|
||||
line = fileReader.Text()
|
||||
parts = strings.Split(line, "/")
|
||||
if len(parts) == 3 {
|
||||
if strings.HasSuffix(parts[1], "docker") {
|
||||
break
|
||||
} else {
|
||||
parts = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parts[len(parts)-1:][0], nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user