diff --git a/cmd/control/service.go b/cmd/control/service.go index 2f48f057..3a135f2d 100644 --- a/cmd/control/service.go +++ b/cmd/control/service.go @@ -11,7 +11,7 @@ import ( "github.com/docker/libcompose/project" "github.com/rancher/os/compose" "github.com/rancher/os/config" - "github.com/rancher/os/util" + "github.com/rancher/os/util/network" ) type projectFactory struct { @@ -172,7 +172,7 @@ func list(c *cli.Context) { clone[service] = enabled } - services, err := util.GetServices(cfg.Rancher.Repositories.ToArray()) + services, err := network.GetServices(cfg.Rancher.Repositories.ToArray()) if err != nil { logrus.Fatalf("Failed to get services: %v", err) } diff --git a/compose/project.go b/compose/project.go index d15a527f..d6f98dfc 100644 --- a/compose/project.go +++ b/compose/project.go @@ -2,6 +2,7 @@ package compose import ( "fmt" + log "github.com/Sirupsen/logrus" yaml "github.com/cloudfoundry-incubator/candiedyaml" "github.com/docker/libcompose/cli/logger" @@ -10,6 +11,7 @@ import ( "github.com/rancher/os/config" rosDocker "github.com/rancher/os/docker" "github.com/rancher/os/util" + "github.com/rancher/os/util/network" ) func CreateService(cfg *config.CloudConfig, name string, serviceConfig *project.ServiceConfig) (project.Service, error) { @@ -121,7 +123,7 @@ func adjustContainerNames(m map[interface{}]interface{}) map[interface{}]interfa return m } -func newCoreServiceProject(cfg *config.CloudConfig, network bool) (*project.Project, error) { +func newCoreServiceProject(cfg *config.CloudConfig, useNetwork bool) (*project.Project, error) { projectEvents := make(chan project.Event) enabled := map[interface{}]interface{}{} @@ -151,9 +153,9 @@ func newCoreServiceProject(cfg *config.CloudConfig, network bool) (*project.Proj continue } - bytes, err := LoadServiceResource(service, network, cfg) + bytes, err := LoadServiceResource(service, useNetwork, cfg) if err != nil { - if err == util.ErrNoNetwork { + if err == network.ErrNoNetwork { log.Debugf("Can not load %s, networking not enabled", service) } else { log.Errorf("Failed to load %s : %v", service, err) @@ -186,7 +188,7 @@ func newCoreServiceProject(cfg *config.CloudConfig, network bool) (*project.Proj go func() { for event := range projectEvents { if event.EventType == project.EventContainerStarted && event.ServiceName == "ntp" { - network = true + useNetwork = true } } }() @@ -240,6 +242,6 @@ func StageServices(cfg *config.CloudConfig, services ...string) error { return p.Pull() } -func LoadServiceResource(name string, network bool, cfg *config.CloudConfig) ([]byte, error) { - return util.LoadResource(name, network, cfg.Rancher.Repositories.ToArray()) +func LoadServiceResource(name string, useNetwork bool, cfg *config.CloudConfig) ([]byte, error) { + return network.LoadResource(name, useNetwork, cfg.Rancher.Repositories.ToArray()) } diff --git a/docker/env.go b/docker/env.go index 4e044977..907c167e 100644 --- a/docker/env.go +++ b/docker/env.go @@ -27,11 +27,30 @@ func appendEnv(array []string, key, value string) []string { return append(array, fmt.Sprintf("%s=%s", key, value)) } +func environmentFromCloudConfig(cfg *config.CloudConfig) map[string]string { + environment := cfg.Rancher.Environment + if cfg.Rancher.Network.HttpProxy != "" { + environment["http_proxy"] = cfg.Rancher.Network.HttpProxy + environment["HTTP_PROXY"] = cfg.Rancher.Network.HttpProxy + } + if cfg.Rancher.Network.HttpsProxy != "" { + environment["https_proxy"] = cfg.Rancher.Network.HttpsProxy + environment["HTTPS_PROXY"] = cfg.Rancher.Network.HttpsProxy + } + if cfg.Rancher.Network.NoProxy != "" { + environment["no_proxy"] = cfg.Rancher.Network.NoProxy + environment["NO_PROXY"] = cfg.Rancher.Network.NoProxy + } + return environment +} + func lookupKeys(cfg *config.CloudConfig, keys ...string) []string { + environment := environmentFromCloudConfig(cfg) + for _, key := range keys { if strings.HasSuffix(key, "*") { result := []string{} - for envKey, envValue := range cfg.Rancher.Environment { + for envKey, envValue := range environment { keyPrefix := key[:len(key)-1] if strings.HasPrefix(envKey, keyPrefix) { result = appendEnv(result, envKey, envValue) @@ -41,7 +60,7 @@ func lookupKeys(cfg *config.CloudConfig, keys ...string) []string { if len(result) > 0 { return result } - } else if value, ok := cfg.Rancher.Environment[key]; ok { + } else if value, ok := environment[key]; ok { return appendEnv([]string{}, key, value) } } diff --git a/init/init.go b/init/init.go index dc8f66cf..888a53e3 100644 --- a/init/init.go +++ b/init/init.go @@ -14,6 +14,7 @@ import ( "github.com/rancher/docker-from-scratch" "github.com/rancher/os/config" "github.com/rancher/os/util" + "github.com/rancher/os/util/network" ) const ( @@ -223,6 +224,10 @@ func RunInit() error { func(c *config.CloudConfig) (*config.CloudConfig, error) { return c, dockerlaunch.PrepareFs(&mountConfig) }, + func(c *config.CloudConfig) (*config.CloudConfig, error) { + network.SetProxyEnvironmentVariables(c) + return c, nil + }, initializeSelinux, func(c *config.CloudConfig) (*config.CloudConfig, error) { return c, syscall.Mount("", "/", "", syscall.MS_SHARED|syscall.MS_REC, "") diff --git a/os-config.tpl.yml b/os-config.tpl.yml index b5f63fd3..726a4e4b 100644 --- a/os-config.tpl.yml +++ b/os-config.tpl.yml @@ -339,6 +339,10 @@ rancher: - /opt:/opt docker: image: {{.OS_IMAGES_ROOT}}/os-docker:{{.VERSION}}{{.SUFFIX}} + environment: + - HTTP_PROXY + - HTTPS_PROXY + - NO_PROXY labels: io.rancher.os.scope: system io.rancher.os.after: console diff --git a/tests/integration/assets/test_17/cloud-config.yml b/tests/integration/assets/test_17/cloud-config.yml new file mode 100644 index 00000000..4aeef649 --- /dev/null +++ b/tests/integration/assets/test_17/cloud-config.yml @@ -0,0 +1,8 @@ +#cloud-config +rancher: + network: + http_proxy: invalid + https_proxy: invalid + no_proxy: invalid +ssh_authorized_keys: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC85w9stZyiLQp/DkVO6fqwiShYcj1ClKdtCqgHtf+PLpJkFReSFu8y21y+ev09gsSMRRrjF7yt0pUHV6zncQhVeqsZtgc5WbELY2DOYUGmRn/CCvPbXovoBrQjSorqlBmpuPwsStYLr92Xn+VVsMNSUIegHY22DphGbDKG85vrKB8HxUxGIDxFBds/uE8FhSy+xsoyT/jUZDK6pgq2HnGl6D81ViIlKecpOpWlW3B+fea99ADNyZNVvDzbHE5pcI3VRw8u59WmpWOUgT6qacNVACl8GqpBvQk8sw7O/X9DSZHCKafeD9G5k+GYbAUz92fKWrx/lOXfUXPS3+c8dRIF diff --git a/tests/integration/rostest/test_17_http_proxy.py b/tests/integration/rostest/test_17_http_proxy.py new file mode 100644 index 00000000..f2601bf7 --- /dev/null +++ b/tests/integration/rostest/test_17_http_proxy.py @@ -0,0 +1,45 @@ +import pytest +import rostest.util as u +from rostest.util import SSH + +cloud_config_path = './tests/integration/assets/test_17/cloud-config.yml' + + +@pytest.fixture(scope="module") +def qemu(request): + q = u.run_qemu(request, run_args=['--cloud-config', cloud_config_path]) + u.flush_out(q.stdout) + return q + + +def test_docker_http_proxy(qemu): + SSH(qemu).check_call(''' +set -x -e + +sudo system-docker exec docker env | grep HTTP_PROXY=invalid +sudo system-docker exec docker env | grep HTTPS_PROXY=invalid +sudo system-docker exec docker env | grep NO_PROXY=invalid + +if docker pull busybox; then + exit 1 +else + exit 0 +fi + ''') + + +def test_system_docker_http_proxy(qemu): + try: + SSH(qemu).check_call('sudo reboot') + except: + pass + + SSH(qemu).check_call(''' +set -x -e + +if sudo system-docker pull busybox; then + exit 1 +else + exit 0 +fi + ''') diff --git a/util/network/network.go b/util/network/network.go new file mode 100644 index 00000000..4dc30ff0 --- /dev/null +++ b/util/network/network.go @@ -0,0 +1,120 @@ +package network + +import ( + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + "strings" + + yaml "github.com/cloudfoundry-incubator/candiedyaml" + + log "github.com/Sirupsen/logrus" + "github.com/rancher/os/config" +) + +var ( + ErrNoNetwork = errors.New("Networking not available to load resource") + ErrNotFound = errors.New("Failed to find resource") +) + +func GetServices(urls []string) ([]string, error) { + result := []string{} + + for _, url := range urls { + indexUrl := fmt.Sprintf("%s/index.yml", url) + content, err := LoadResource(indexUrl, true, []string{}) + if err != nil { + log.Errorf("Failed to load %s: %v", indexUrl, err) + continue + } + + services := make(map[string][]string) + err = yaml.Unmarshal(content, &services) + if err != nil { + log.Errorf("Failed to unmarshal %s: %v", indexUrl, err) + continue + } + + if list, ok := services["services"]; ok { + result = append(result, list...) + } + } + + return result, nil +} + +func SetProxyEnvironmentVariables(cfg *config.CloudConfig) { + if cfg.Rancher.Network.HttpProxy != "" { + err := os.Setenv("HTTP_PROXY", cfg.Rancher.Network.HttpProxy) + if err != nil { + log.Errorf("Unable to set HTTP_PROXY: %s", err) + } + } + if cfg.Rancher.Network.HttpsProxy != "" { + err := os.Setenv("HTTPS_PROXY", cfg.Rancher.Network.HttpsProxy) + if err != nil { + log.Errorf("Unable to set HTTPS_PROXY: %s", err) + } + } + if cfg.Rancher.Network.NoProxy != "" { + err := os.Setenv("NO_PROXY", cfg.Rancher.Network.NoProxy) + if err != nil { + log.Errorf("Unable to set NO_PROXY: %s", err) + } + } +} + +func retryHttp(f func() (*http.Response, error), times int) (resp *http.Response, err error) { + for i := 0; i < times; i++ { + if resp, err = f(); err == nil { + return + } + log.Warnf("Error making HTTP request: %s. Retrying", err) + } + return +} + +func LoadResource(location string, network bool, urls []string) ([]byte, error) { + var bytes []byte + err := ErrNotFound + + if strings.HasPrefix(location, "http:/") || strings.HasPrefix(location, "https:/") { + if !network { + return nil, ErrNoNetwork + } + + cfg, err := config.LoadConfig() + if err != nil { + return nil, err + } + + SetProxyEnvironmentVariables(cfg) + + resp, err := retryHttp(func() (*http.Response, error) { + return http.Get(location) + }, 8) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("non-200 http response: %d", resp.StatusCode) + } + defer resp.Body.Close() + return ioutil.ReadAll(resp.Body) + } else if strings.HasPrefix(location, "/") { + return ioutil.ReadFile(location) + } else if len(location) > 0 { + for _, url := range urls { + ymlUrl := fmt.Sprintf("%s/%s/%s.yml", url, location[0:1], location) + bytes, err = LoadResource(ymlUrl, network, []string{}) + if err == nil { + log.Debugf("Loaded %s from %s", location, ymlUrl) + return bytes, nil + } + } + } + + return nil, err +} diff --git a/util/network/network_test.go b/util/network/network_test.go new file mode 100644 index 00000000..a441d17d --- /dev/null +++ b/util/network/network_test.go @@ -0,0 +1,23 @@ +package network + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func NoTestLoadResourceSimple(t *testing.T) { + assert := require.New(t) + + expected := `services: +- debian-console +- ubuntu-console +` + expected = strings.TrimSpace(expected) + + b, e := LoadResource("https://raw.githubusercontent.com/rancher/os-services/v0.3.4/index.yml", true, []string{}) + + assert.Nil(e) + assert.Equal(expected, strings.TrimSpace(string(b))) +} diff --git a/util/util.go b/util/util.go index c6b7365a..ee1d0ffb 100644 --- a/util/util.go +++ b/util/util.go @@ -2,11 +2,8 @@ package util import ( "bytes" - "errors" - "fmt" "io" "io/ioutil" - "net/http" "os" "strings" @@ -17,11 +14,6 @@ import ( "reflect" ) -var ( - ErrNoNetwork = errors.New("Networking not available to load resource") - ErrNotFound = errors.New("Failed to find resource") -) - type AnyMap map[interface{}]interface{} func Contains(values []string, value string) bool { @@ -256,32 +248,6 @@ func ToStrings(data []interface{}) []string { return result } -func GetServices(urls []string) ([]string, error) { - result := []string{} - - for _, url := range urls { - indexUrl := fmt.Sprintf("%s/index.yml", url) - content, err := LoadResource(indexUrl, true, []string{}) - if err != nil { - log.Errorf("Failed to load %s: %v", indexUrl, err) - continue - } - - services := make(map[string][]string) - err = yaml.Unmarshal(content, &services) - if err != nil { - log.Errorf("Failed to unmarshal %s: %v", indexUrl, err) - continue - } - - if list, ok := services["services"]; ok { - result = append(result, list...) - } - } - - return result, nil -} - func DirLs(dir string) ([]interface{}, error) { result := []interface{}{} files, err := ioutil.ReadDir(dir) @@ -294,49 +260,6 @@ func DirLs(dir string) ([]interface{}, error) { return result, nil } -func retryHttp(f func() (*http.Response, error), times int) (resp *http.Response, err error) { - for i := 0; i < times; i++ { - if resp, err = f(); err == nil { - return - } - log.Warnf("Error making HTTP request: %s. Retrying", err) - } - return -} - -func LoadResource(location string, network bool, urls []string) ([]byte, error) { - var bytes []byte - err := ErrNotFound - - if strings.HasPrefix(location, "http:/") || strings.HasPrefix(location, "https:/") { - if !network { - return nil, ErrNoNetwork - } - resp, err := retryHttp(func() (*http.Response, error) { return http.Get(location) }, 8) - if err != nil { - return nil, err - } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("non-200 http response: %d", resp.StatusCode) - } - defer resp.Body.Close() - return ioutil.ReadAll(resp.Body) - } else if strings.HasPrefix(location, "/") { - return ioutil.ReadFile(location) - } else if len(location) > 0 { - for _, url := range urls { - ymlUrl := fmt.Sprintf("%s/%s/%s.yml", url, location[0:1], location) - bytes, err = LoadResource(ymlUrl, network, []string{}) - if err == nil { - log.Debugf("Loaded %s from %s", location, ymlUrl) - return bytes, nil - } - } - } - - return nil, err -} - func Map2KVPairs(m map[string]string) []string { r := make([]string, 0, len(m)) for k, v := range m { diff --git a/util/util_test.go b/util/util_test.go index 53c0679d..37327081 100644 --- a/util/util_test.go +++ b/util/util_test.go @@ -1,9 +1,9 @@ package util import ( - "github.com/stretchr/testify/require" - "strings" "testing" + + "github.com/stretchr/testify/require" ) type testCloudConfig struct { @@ -180,18 +180,3 @@ func TestMapsUnion(t *testing.T) { } assert.Equal(expected, MapsUnion(m0, m1)) } - -func NoTestLoadResourceSimple(t *testing.T) { - assert := require.New(t) - - expected := `services: -- debian-console -- ubuntu-console -` - expected = strings.TrimSpace(expected) - - b, e := LoadResource("https://raw.githubusercontent.com/rancher/os-services/v0.3.4/index.yml", true, []string{}) - - assert.Nil(e) - assert.Equal(expected, strings.TrimSpace(string(b))) -}