1
0
mirror of https://github.com/rancher/os.git synced 2025-06-29 16:26:50 +00:00

Merge pull request #980 from joshwget/first-class-consoles

First class consoles
This commit is contained in:
Darren Shepherd 2016-06-06 15:27:23 -07:00
commit cd76d85aea
16 changed files with 302 additions and 103 deletions

View File

@ -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
View 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
}

View File

@ -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
View 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"
}

View File

@ -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
}

View 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)
}
}

View File

@ -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
View 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
}
}

View File

@ -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"`

View File

@ -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,

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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...)
}
}

View File

@ -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
}