Skip to content

Commit 5441619

Browse files
committed
Handle errors when couldn't download config or docker-compose
Signed-off-by: David Pordomingo <[email protected]>
1 parent d3021a0 commit 5441619

File tree

8 files changed

+100
-25
lines changed

8 files changed

+100
-25
lines changed

cmd/sourced/cmd/compose.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func (c *composeDownloadCmd) Execute(args []string) error {
2727
v = version
2828
}
2929

30-
err := composefile.Download(v)
30+
err := composefile.ActivateFromRemote(v)
3131
if err != nil {
3232
return err
3333
}

cmd/sourced/cmd/root.go

+20
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"os"
66
"runtime"
77

8+
"github.com/src-d/sourced-ce/cmd/sourced/compose"
9+
"github.com/src-d/sourced-ce/cmd/sourced/compose/file"
810
"github.com/src-d/sourced-ce/cmd/sourced/compose/workdir"
911
"github.com/src-d/sourced-ce/cmd/sourced/dir"
1012
"github.com/src-d/sourced-ce/cmd/sourced/format"
@@ -65,11 +67,29 @@ func log(err error) {
6567
printRed("Cannot perform this action, full re-initialization is needed, run 'prune' command first")
6668
case dir.ErrNotValid.Is(err):
6769
printRed("Cannot perform this action, config directory is not valid")
70+
case compose.ErrComposeAlternative.Is(err):
71+
printRed("docker-compose is not installed, and there was an error while trying to use the container alternative")
72+
printRed(" see: https://docs.sourced.tech/community-edition/quickstart/1-install-requirements#docker-compose")
73+
case file.ErrConfigDownload.Is(err):
74+
printRed("The source{d} CE config file could not be set as active")
6875
case fmt.Sprintf("%T", err) == "*flags.Error":
6976
// syntax error is already logged by go-cli
7077
default:
7178
// unknown errors have no special message
7279
}
80+
81+
switch {
82+
case dir.ErrNetwork.Is(err):
83+
// TODO(dpordomingo): if start using "https://golang.org/pkg/errors/",
84+
// we could do `var myErr ErrNetwork; errors.As(err, &myErr)`
85+
// to provide more info about the actual network error
86+
printRed("The resource could not be downloaded from the Internet")
87+
printRed(" see: https://docs.sourced.tech/community-edition/quickstart/1-install-requirements#internet-connection")
88+
case dir.ErrWrite.Is(err):
89+
// TODO(dpordomingo): see todo above
90+
printRed("could not write file")
91+
92+
}
7393
}
7494

7595
func printRed(message string) {

cmd/sourced/compose/compose.go

+14-9
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/src-d/sourced-ce/cmd/sourced/dir"
1515

1616
"github.com/pkg/errors"
17+
goerrors "gopkg.in/src-d/go-errors.v1"
1718
)
1819

1920
// dockerComposeVersion is the version of docker-compose to download
@@ -22,6 +23,9 @@ const dockerComposeVersion = "1.24.0"
2223

2324
var composeContainerURL = fmt.Sprintf("https://github.com/docker/compose/releases/download/%s/run.sh", dockerComposeVersion)
2425

26+
// ErrComposeAlternative is returned when docker-compose alternative could not be installed
27+
var ErrComposeAlternative = goerrors.NewKind("error while trying docker-compose container alternative")
28+
2529
type Compose struct {
2630
bin string
2731
workdirHandler *workdir.Handler
@@ -81,13 +85,13 @@ func getOrInstallComposeBinary() (string, error) {
8185

8286
path, err = getOrInstallComposeContainer()
8387
if err != nil {
84-
return "", errors.Wrapf(err, "error while getting docker-compose container")
88+
return "", ErrComposeAlternative.Wrap(err)
8589
}
8690

8791
return path, nil
8892
}
8993

90-
func getOrInstallComposeContainer() (string, error) {
94+
func getOrInstallComposeContainer() (altPath string, err error) {
9195
datadir, err := dir.Path()
9296
if err != nil {
9397
return "", err
@@ -96,19 +100,20 @@ func getOrInstallComposeContainer() (string, error) {
96100
dirPath := filepath.Join(datadir, "bin")
97101
path := filepath.Join(dirPath, fmt.Sprintf("docker-compose-%s.sh", dockerComposeVersion))
98102

99-
if _, err := os.Stat(path); err == nil {
103+
readExecAccessMode := os.FileMode(0500)
104+
105+
if info, err := os.Stat(path); err == nil {
106+
if info.Mode()&readExecAccessMode != readExecAccessMode {
107+
return "", fmt.Errorf("%s can not be run", path)
108+
}
109+
100110
return path, nil
101111
} else if !os.IsNotExist(err) {
102112
return "", err
103113
}
104114

105-
err = os.MkdirAll(dirPath, os.ModePerm)
106-
if err != nil {
107-
return "", errors.Wrapf(err, "error while creating directory %s", dirPath)
108-
}
109-
110115
if err := downloadCompose(path); err != nil {
111-
return "", errors.Wrapf(err, "error downloading %s", composeContainerURL)
116+
return "", err
112117
}
113118

114119
cmd := exec.CommandContext(context.Background(), "chmod", "+x", path)

cmd/sourced/compose/file/file.go

+18-6
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,15 @@ import (
1313
datadir "github.com/src-d/sourced-ce/cmd/sourced/dir"
1414

1515
"github.com/pkg/errors"
16+
goerrors "gopkg.in/src-d/go-errors.v1"
1617
)
1718

19+
// ErrConfigDownload is returned when docker-compose.yml could not be downloaded
20+
var ErrConfigDownload = goerrors.NewKind("docker-compose.yml config file could not be downloaded")
21+
22+
// ErrConfigActivation is returned when docker-compose.yml could not be set as active
23+
var ErrConfigActivation = goerrors.NewKind("docker-compose.yml could not be set as active")
24+
1825
const (
1926
orgName = "src-d"
2027
repoName = "sourced-ce"
@@ -61,7 +68,7 @@ func InitDefault() (string, error) {
6168
return "", err
6269
}
6370

64-
err = Download(version)
71+
err = ActivateFromRemote(version)
6572
if err != nil {
6673
return "", err
6774
}
@@ -96,9 +103,9 @@ func InitDefaultOverride() (string, error) {
96103
return globalOverridePath, nil
97104
}
98105

99-
// Download downloads the docker-compose.yml file from the given revision
100-
// or URL. The file is set as the active compose file.
101-
func Download(revOrURL RevOrURL) error {
106+
// ActivateFromRemote downloads the docker-compose.yml file from the given revision
107+
// or URL, and sets it as the active compose file.
108+
func ActivateFromRemote(revOrURL RevOrURL) (err error) {
102109
var url string
103110
if isURL(revOrURL) {
104111
url = revOrURL
@@ -113,10 +120,15 @@ func Download(revOrURL RevOrURL) error {
113120

114121
err = datadir.DownloadURL(url, outPath)
115122
if err != nil {
116-
return err
123+
return ErrConfigDownload.Wrap(err)
124+
}
125+
126+
err = SetActive(revOrURL)
127+
if err != nil {
128+
return ErrConfigActivation.Wrap(err)
117129
}
118130

119-
return SetActive(revOrURL)
131+
return nil
120132
}
121133

122134
// SetActive makes a symlink from

cmd/sourced/dir/dir.go

+16-6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ var ErrNotExist = goerrors.NewKind("%s does not exist")
1818
// ErrNotValid is returned when config dir is not valid
1919
var ErrNotValid = goerrors.NewKind("%s is not a valid config directory: %s")
2020

21+
// ErrNetwork is returned when could not download
22+
var ErrNetwork = goerrors.NewKind("network error downloading %s")
23+
24+
// ErrWrite is returned when could not write
25+
var ErrWrite = goerrors.NewKind("write error at %s")
26+
2127
// Path returns the absolute path for $SOURCED_DIR, or $HOME/.sourced if unset
2228
// and returns an error if it does not exist or it could not be read.
2329
func Path() (string, error) {
@@ -96,29 +102,33 @@ func validate(path string) error {
96102

97103
// DownloadURL downloads the given url to a file to the
98104
// dst path, creating the directory if it's needed
99-
func DownloadURL(url, dst string) error {
105+
func DownloadURL(url, dst string) (err error) {
100106
resp, err := http.Get(url)
101107
if err != nil {
102-
return err
108+
return ErrNetwork.Wrap(err, url)
103109
}
104110
defer resp.Body.Close()
105111

106112
if resp.StatusCode != http.StatusOK {
107-
return fmt.Errorf("HTTP status %v", resp.Status)
113+
return ErrNetwork.Wrap(fmt.Errorf("HTTP status %v", resp.Status), url)
108114
}
109115

110116
if err := os.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil {
111-
return err
117+
return ErrWrite.Wrap(err, filepath.Dir(dst))
112118
}
113119

114120
out, err := os.Create(dst)
115121
if err != nil {
116-
return err
122+
return ErrWrite.Wrap(err, dst)
117123
}
118124
defer out.Close()
119125

120126
_, err = io.Copy(out, resp.Body)
121-
return err
127+
if err != nil {
128+
return ErrWrite.Wrap(err, dst)
129+
}
130+
131+
return nil
122132
}
123133

124134
// TmpPath returns the absolute path for /tmp/srcd

cmd/sourced/dir/dir_test.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package dir
22

33
import (
4+
"fmt"
45
"io/ioutil"
56
"net/http"
67
"net/http/httptest"
78
"os"
89
"path"
910
"testing"
1011

12+
"github.com/pkg/errors"
1113
"github.com/stretchr/testify/assert"
1214
)
1315

@@ -114,5 +116,10 @@ func TestDownloadURL(t *testing.T) {
114116
}
115117
server = httptest.NewServer(http.HandlerFunc(handler))
116118
err = DownloadURL(server.URL, "/dev/null")
117-
assert.EqualError(err, "HTTP status 404 Not Found")
119+
errExpected := errors.Wrapf(
120+
fmt.Errorf("HTTP status %v", "404 Not Found"),
121+
"network error downloading %s", server.URL,
122+
)
123+
124+
assert.EqualError(err, errExpected.Error())
118125
}

docs/learn-more/faq.md

+15
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ _For tips and advices to deal with unexpected errors, please refer to [Troublesh
1010
- [Can I Query Gitbase or Babelfish with External Tools?](#can-i-query-gitbase-or-babelfish-with-external-tools)
1111
- [Where Can I Read More About the Web Interface?](#where-can-i-read-more-about-the-web-interface)
1212
- [I Get IOError Permission denied](#i-get-ioerror-permission-denied)
13+
- [Why Do I Need Internet Connection?](#why-do-i-need-internet-connection)
14+
1315

1416
## Where Can I Find More Assistance to Run source{d} or Notify You About Any Issue or Suggestion?
1517

@@ -118,6 +120,7 @@ The user interface is based in the open-sourced [Apache Superset](http://superse
118120
so you can also refer to [Superset tutorials](http://superset.apache.org/tutorial.html)
119121
for advanced usage of the web interface.
120122

123+
121124
## I Get IOError Permission denied
122125

123126
If you get this error message:
@@ -127,3 +130,15 @@ IOError: [Errno 13] Permission denied: u'./.env'
127130
```
128131

129132
This may happen if you have installed Docker from a snap package. This installation mode is not supported, please install it following [the official documentation](./quickstart/1-install-requirements.md#install-docker) (See [#78](https://github.com/src-d/sourced-ce/issues/78)).
133+
134+
135+
## Why Do I Need Internet Connection?
136+
137+
source{d} CE automatically fetches some resources from the Internet when they are not found locally:
138+
139+
- the source{d} CE configuration is fetched automatically when initializing it for the first time, using the proper version for the current version of `sourced`, e.g. if using `v0.16.0` it will automatically fetch `https://raw.githubusercontent.com/src-d/sourced-ce/v0.16.0/docker-compose.yml`.
140+
- to download the docker images of the source{d} CE components when initializing source{d} for the first time, or when initializing it after changing its configuration.
141+
- to download repositories and its metadata from GitHub when you initialize source{d} CE with `sourced init orgs`.
142+
- to download and install [Docker Compose alternative](#docker-compose) if there is no local installation of Docker Compose.
143+
144+
If your connection to the network does not let source{d} CE to access to Internet, you should manually provide all these dependencies.

docs/quickstart/1-install-requirements.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ Follow the instructions based on your OS:
1515

1616
## Docker Compose
1717

18-
**source{d} CE** is deployed as Docker containers, using [Docker Compose](https://docs.docker.com/compose), but it is not required to have a local installation of Docker Compose; if it is not found it will be deployed inside a container.
18+
**source{d} CE** is deployed as Docker containers, using [Docker Compose](https://docs.docker.com/compose), but it is not required to have a local installation of Docker Compose; if it is not found it will be downloaded from docker sources, and deployed inside a container.
1919

20-
If you prefer a local installation of Docker Compose, you can follow the [Docker Compose install guide](https://docs.docker.com/compose/install)
20+
If you prefer a local installation of Docker Compose, or you have no access to internet to download it, you can follow the [Docker Compose install guide](https://docs.docker.com/compose/install)
21+
22+
## Internet Connection
23+
24+
source{d} CE automatically fetches some resources from the Internet when they are not found locally, so in order to use all source{d} CE capacities, Internet connection is needed.
25+
26+
For more details, you can refer to [Why Do I Need Internet Connection?](../learn-more/faq.md#why-do-i-need-internet-connection)

0 commit comments

Comments
 (0)