1
0
mirror of https://github.com/rancher/os.git synced 2025-06-28 07:46:49 +00:00
os/cmd/control/service/service.go
Sven Dowideit 355859e707 golint&gofmt
Signed-off-by: Sven Dowideit <SvenDowideit@home.org.au>
2017-03-13 12:56:54 +00:00

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
}