mirror of
https://github.com/rancher/os.git
synced 2025-06-28 07:46:49 +00:00
394 lines
9.9 KiB
Go
Executable File
394 lines
9.9 KiB
Go
Executable File
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
|
"github.com/codegangsta/cli"
|
|
dockerApp "github.com/docker/libcompose/cli/docker/app"
|
|
composeConfig "github.com/docker/libcompose/config"
|
|
|
|
"github.com/docker/libcompose/project/options"
|
|
|
|
"github.com/docker/libcompose/project"
|
|
"github.com/rancher/os/cmd/control/service/command"
|
|
"github.com/rancher/os/compose"
|
|
"github.com/rancher/os/config"
|
|
"github.com/rancher/os/log"
|
|
"github.com/rancher/os/util"
|
|
"github.com/rancher/os/util/network"
|
|
)
|
|
|
|
type ProjectFactory struct {
|
|
}
|
|
|
|
func (p *ProjectFactory) Create(c *cli.Context) (project.APIProject, error) {
|
|
cfg := config.LoadConfig()
|
|
return compose.GetProject(cfg, true, false)
|
|
}
|
|
|
|
func beforeApp(c *cli.Context) error {
|
|
if c.GlobalBool("verbose") {
|
|
log.SetLevel(log.DebugLevel)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func Commands() cli.Command {
|
|
factory := &ProjectFactory{}
|
|
|
|
app := cli.Command{}
|
|
app.Name = "service"
|
|
app.ShortName = "s"
|
|
app.Usage = "Command line interface for services and compose."
|
|
app.Before = beforeApp
|
|
app.Flags = append(dockerApp.DockerClientFlags(), cli.BoolFlag{
|
|
Name: "verbose,debug",
|
|
})
|
|
app.Subcommands = append(serviceSubCommands(),
|
|
command.BuildCommand(factory),
|
|
command.CreateCommand(factory),
|
|
command.UpCommand(factory),
|
|
command.StartCommand(factory),
|
|
command.LogsCommand(factory),
|
|
command.RestartCommand(factory),
|
|
command.StopCommand(factory),
|
|
command.RmCommand(factory),
|
|
command.PullCommand(factory),
|
|
command.KillCommand(factory),
|
|
command.PsCommand(factory),
|
|
)
|
|
|
|
return app
|
|
}
|
|
|
|
func serviceSubCommands() []cli.Command {
|
|
return []cli.Command{
|
|
{
|
|
Name: "enable",
|
|
Usage: "turn on an service",
|
|
Action: Enable,
|
|
},
|
|
{
|
|
Name: "disable",
|
|
Usage: "turn off an service",
|
|
Action: disable,
|
|
},
|
|
{
|
|
Name: "list",
|
|
Usage: "list services and state",
|
|
Action: list,
|
|
},
|
|
{
|
|
Name: "delete",
|
|
Usage: "delete a service",
|
|
Action: Del,
|
|
},
|
|
}
|
|
}
|
|
|
|
func updateIncludedServices(cfg *config.CloudConfig) error {
|
|
return config.Set("rancher.services_include", cfg.Rancher.ServicesInclude)
|
|
}
|
|
|
|
func disable(c *cli.Context) error {
|
|
changed := false
|
|
cfg := config.LoadConfig()
|
|
|
|
for _, service := range c.Args() {
|
|
validateService(service, cfg)
|
|
|
|
if _, ok := cfg.Rancher.ServicesInclude[service]; !ok {
|
|
continue
|
|
}
|
|
|
|
cfg.Rancher.ServicesInclude[service] = false
|
|
changed = true
|
|
}
|
|
|
|
if changed {
|
|
if err := updateIncludedServices(cfg); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func Del(c *cli.Context) error {
|
|
changed := false
|
|
cfg := config.LoadConfig()
|
|
|
|
for _, service := range c.Args() {
|
|
validateService(service, cfg)
|
|
|
|
if _, ok := cfg.Rancher.ServicesInclude[service]; !ok {
|
|
continue
|
|
}
|
|
|
|
delete(cfg.Rancher.ServicesInclude, service)
|
|
changed = true
|
|
}
|
|
|
|
if changed {
|
|
if err := updateIncludedServices(cfg); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ComposeToCloudConfig(bytes []byte) ([]byte, error) {
|
|
//TODO: copied from cloudinitsave, move to config.
|
|
compose := make(map[interface{}]interface{})
|
|
err := yaml.Unmarshal(bytes, &compose)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return yaml.Marshal(map[interface{}]interface{}{
|
|
"rancher": map[interface{}]interface{}{
|
|
"services": compose,
|
|
},
|
|
})
|
|
}
|
|
|
|
func LoadService(repoName, serviceLongName string) (*config.CloudConfig, error) {
|
|
// TODO: this should move to something like config/service.go?
|
|
// WARNING: this can contain more than one service - Josh and I aren't sure this is worth it
|
|
servicePath := fmt.Sprintf("%s/%s.yml", repoName, serviceLongName)
|
|
//log.Infof("loading %s", serviceLongName)
|
|
content, err := network.CacheLookup(servicePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to load %s: %v", servicePath, err)
|
|
}
|
|
if content, err = ComposeToCloudConfig(content); err != nil {
|
|
return nil, fmt.Errorf("Failed to convert compose to cloud-config syntax: %v", err)
|
|
}
|
|
|
|
p, err := config.ReadConfig(content, true)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to load %s : %v", servicePath, err)
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func IsConsole(serviceCfg *config.CloudConfig) bool {
|
|
// TODO: this should move to something like config/service.go?
|
|
//the service is called console, and has an io.rancher.os.console label.
|
|
for serviceName, service := range serviceCfg.Rancher.Services {
|
|
if serviceName == "console" {
|
|
for k := range service.Labels {
|
|
if k == "io.rancher.os.console" {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func IsEngine(serviceCfg *config.CloudConfig) bool {
|
|
// TODO: this should move to something like config/service.go?
|
|
//the service is called docker, and the command is "ros user-docker"
|
|
for serviceName, service := range serviceCfg.Rancher.Services {
|
|
log.Infof("serviceName == %s", serviceName)
|
|
if serviceName == "docker" {
|
|
cmd := strings.Join(service.Command, " ")
|
|
log.Infof("service command == %s", cmd)
|
|
if cmd == "ros user-docker" {
|
|
log.Infof("yes, its a docker engine")
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func Enable(c *cli.Context) error {
|
|
cfg := config.LoadConfig()
|
|
|
|
var enabledServices []string
|
|
var consoleService, engineService string
|
|
var errorServices []string
|
|
serviceMap := make(map[string]*config.CloudConfig)
|
|
|
|
for _, service := range c.Args() {
|
|
//validateService(service, cfg)
|
|
//log.Infof("start4")
|
|
// TODO: need to search for the service in all the repos.
|
|
// TODO: also need to deal with local file paths and URLs
|
|
serviceConfig, err := LoadService("core", service)
|
|
if err != nil {
|
|
log.Errorf("Failed to load %s: %s", service, err)
|
|
errorServices = append(errorServices, service)
|
|
continue
|
|
}
|
|
serviceMap[service] = serviceConfig
|
|
}
|
|
if len(serviceMap) == 0 {
|
|
log.Fatalf("No valid Services found")
|
|
}
|
|
if len(errorServices) > 0 {
|
|
if c.Bool("force") || !util.Yes("Some services failed to load, Continue?") {
|
|
log.Fatalf("Services failed to load: %v", errorServices)
|
|
}
|
|
}
|
|
|
|
for service, serviceConfig := range serviceMap {
|
|
if val, ok := cfg.Rancher.ServicesInclude[service]; !ok || !val {
|
|
if isLocal(service) && !strings.HasPrefix(service, "/var/lib/rancher/conf") {
|
|
log.Fatalf("ERROR: Service should be in path /var/lib/rancher/conf")
|
|
}
|
|
|
|
if IsConsole(serviceConfig) {
|
|
log.Debugf("Enabling the %s console", service)
|
|
if err := config.Set("rancher.console", service); err != nil {
|
|
log.Errorf("Failed to update 'rancher.console': %v", err)
|
|
}
|
|
consoleService = service
|
|
|
|
} else if IsEngine(serviceConfig) {
|
|
log.Debugf("Enabling the %s user engine", service)
|
|
if err := config.Set("rancher.docker.engine", service); err != nil {
|
|
log.Errorf("Failed to update 'rancher.docker.engine': %v", err)
|
|
}
|
|
engineService = service
|
|
} else {
|
|
cfg.Rancher.ServicesInclude[service] = true
|
|
}
|
|
enabledServices = append(enabledServices, service)
|
|
}
|
|
}
|
|
|
|
if len(enabledServices) > 0 {
|
|
if err := compose.StageServices(cfg, enabledServices...); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if err := updateIncludedServices(cfg); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
//TODO: fix the case where the user is applying both a new console and a new docker engine
|
|
if consoleService != "" && c.Bool("apply") {
|
|
//ros console switch.
|
|
if !c.Bool("force") {
|
|
fmt.Println(`Switching consoles will
|
|
1. destroy the current console container
|
|
2. log you out
|
|
3. restart Docker`)
|
|
if !util.Yes("Continue") {
|
|
return nil
|
|
}
|
|
switchService, err := compose.CreateService(nil, "switch-console", &composeConfig.ServiceConfigV1{
|
|
LogDriver: "json-file",
|
|
Privileged: true,
|
|
Net: "host",
|
|
Pid: "host",
|
|
Image: config.OsBase,
|
|
Labels: map[string]string{
|
|
config.ScopeLabel: config.System,
|
|
},
|
|
Command: []string{"/usr/bin/ros", "switch-console", consoleService},
|
|
VolumesFrom: []string{"all-volumes"},
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if err = switchService.Delete(context.Background(), options.Delete{}); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if err = switchService.Up(context.Background(), options.Up{}); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if err = switchService.Log(context.Background(), true); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
if engineService != "" && c.Bool("apply") {
|
|
log.Info("Starting the %s engine", engineService)
|
|
project, err := compose.GetProject(cfg, true, false)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if err = project.Stop(context.Background(), 10, "docker"); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if err = compose.LoadSpecialService(project, cfg, "docker", engineService); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if err = project.Up(context.Background(), options.Up{}, "docker"); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func list(c *cli.Context) error {
|
|
cfg := config.LoadConfig()
|
|
|
|
clone := make(map[string]bool)
|
|
for service, enabled := range cfg.Rancher.ServicesInclude {
|
|
clone[service] = enabled
|
|
}
|
|
|
|
services := availableService(cfg)
|
|
|
|
for _, service := range services {
|
|
if enabled, ok := clone[service]; ok {
|
|
delete(clone, service)
|
|
if enabled {
|
|
fmt.Printf("enabled %s\n", service)
|
|
} else {
|
|
fmt.Printf("disabled %s\n", service)
|
|
}
|
|
} else {
|
|
fmt.Printf("disabled %s\n", service)
|
|
}
|
|
}
|
|
|
|
for service, enabled := range clone {
|
|
if enabled {
|
|
fmt.Printf("enabled %s\n", service)
|
|
} else {
|
|
fmt.Printf("disabled %s\n", service)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func isLocal(service string) bool {
|
|
return strings.HasPrefix(service, "/")
|
|
}
|
|
|
|
func IsLocalOrURL(service string) bool {
|
|
return isLocal(service) || strings.HasPrefix(service, "http:/") || strings.HasPrefix(service, "https:/")
|
|
}
|
|
|
|
func validateService(service string, cfg *config.CloudConfig) {
|
|
services := availableService(cfg)
|
|
if !IsLocalOrURL(service) && !util.Contains(services, service) {
|
|
log.Fatalf("%s is not a valid service", service)
|
|
}
|
|
}
|
|
|
|
func availableService(cfg *config.CloudConfig) []string {
|
|
services, err := network.GetServices(cfg.Rancher.Repositories.ToArray())
|
|
if err != nil {
|
|
log.Fatalf("Failed to get services: %v", err)
|
|
}
|
|
return services
|
|
}
|