diff --git a/cmd/buildpack/main.go b/cmd/buildpack/main.go index 3d7691a..3d3ea91 100644 --- a/cmd/buildpack/main.go +++ b/cmd/buildpack/main.go @@ -17,7 +17,8 @@ import ( "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" - "github.com/imjasonh/kontain.me/pkg" + "github.com/imjasonh/kontain.me/pkg/run" + "github.com/imjasonh/kontain.me/pkg/serve" "golang.org/x/oauth2/google" ) @@ -71,9 +72,9 @@ func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { case strings.Contains(path, "/manifests/"): s.serveBuildpackManifest(w, r) case strings.Contains(path, "/blobs/"): - pkg.ServeBlob(w, r) + serve.Blob(w, r) default: - http.Error(w, "not found", http.StatusNotFound) + serve.Error(w, serve.ErrNotFound) } } @@ -82,7 +83,7 @@ func (s *server) serveBuildpackManifest(w http.ResponseWriter, r *http.Request) src, layers, err := s.prepareWorkspace() if err != nil { s.error.Println("ERROR:", err) - http.Error(w, err.Error(), http.StatusInternalServerError) + serve.Error(w, err) return } // Clean up workspace. @@ -113,7 +114,7 @@ func (s *server) serveBuildpackManifest(w http.ResponseWriter, r *http.Request) image, err := s.fetchAndBuild(src, layers, repo, revision) if err != nil { s.error.Println("ERROR:", err) - http.Error(w, err.Error(), http.StatusInternalServerError) + serve.Error(w, err) return } @@ -121,10 +122,10 @@ func (s *server) serveBuildpackManifest(w http.ResponseWriter, r *http.Request) img, err := s.getImage(image) if err != nil { s.error.Println("ERROR:", err) - http.Error(w, err.Error(), http.StatusInternalServerError) + serve.Error(w, err) return } - pkg.ServeManifest(w, img) + serve.Manifest(w, img) } func (s *server) prepareWorkspace() (string, string, error) { @@ -141,7 +142,7 @@ func (s *server) prepareWorkspace() (string, string, error) { } } }`, auth) - if err := pkg.Run(s.info.Writer(), "mkdir -p ~/.docker/ && cat << EOF > ~/.docker/config.json\n"+string(configJSON)+"\nEOF"); err != nil { + if err := run.Do(s.info.Writer(), "mkdir -p ~/.docker/ && cat << EOF > ~/.docker/config.json\n"+string(configJSON)+"\nEOF"); err != nil { return "", "", err } @@ -171,6 +172,14 @@ func (s *server) fetchAndBuild(src, layers, repo, revision string) (string, erro image := fmt.Sprintf("gcr.io/%s/built-%d", projectID, time.Now().Unix) source := fmt.Sprintf("https://github.com/%s/archive/%s.tar.gz", repo, revision) + if resp, err := http.Head(source); err != nil { + return "", err + } else if resp.StatusCode == http.StatusNotFound { + return "", serve.ErrNotFound + } else if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("HEAD %s (%d): %s", source, resp.StatusCode, resp.Status) + } + for _, cmd := range []string{ fmt.Sprintf("chown -R %d:%d %s", os.Geteuid(), os.Getgid(), src), fmt.Sprintf("chown -R %d:%d %s", os.Geteuid(), os.Getgid(), layers), @@ -181,7 +190,7 @@ func (s *server) fetchAndBuild(src, layers, repo, revision string) (string, erro fmt.Sprintf("/lifecycle/builder -layers=%s -app=%s -group=%s/group.toml -plan=%s/plan.toml", layers, src, layers, layers), fmt.Sprintf("/lifecycle/exporter -layers=%s -helpers=true -app=%s -image=%s -group=%s/group.toml %s", layers, src, base, layers, image), } { - if err := pkg.Run(s.info.Writer(), cmd); err != nil { + if err := run.Do(s.info.Writer(), cmd); err != nil { return "", fmt.Errorf("Running %q: %v", cmd, err) } } diff --git a/cmd/ko/main.go b/cmd/ko/main.go index 9edd112..17d070a 100644 --- a/cmd/ko/main.go +++ b/cmd/ko/main.go @@ -32,7 +32,8 @@ import ( "github.com/google/go-containerregistry/pkg/v1/random" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/ko/pkg/build" - "github.com/imjasonh/kontain.me/pkg" + "github.com/imjasonh/kontain.me/pkg/run" + "github.com/imjasonh/kontain.me/pkg/serve" ) func main() { @@ -82,9 +83,9 @@ func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.serveKoManifest(w, r) case strings.HasPrefix(path, "random/blobs/"), strings.HasPrefix(path, "ko/") && strings.Contains(path, "/blobs/"): - pkg.ServeBlob(w, r) + serve.Blob(w, r) default: - http.Error(w, "not found", http.StatusNotFound) + serve.Error(w, serve.ErrNotFound) } } @@ -108,9 +109,9 @@ func (s *server) serveKoManifest(w http.ResponseWriter, r *http.Request) { // go get the package. s.info.Printf("go get %s...", ip) - if err := pkg.Run(s.info.Writer(), fmt.Sprintf("go get %s", ip)); err != nil { + if err := run.Do(s.info.Writer(), fmt.Sprintf("go get %s", ip)); err != nil { s.error.Printf("ERROR (go get): %s", err) - http.Error(w, err.Error(), http.StatusInternalServerError) + serve.Error(w, err) return } // TODO: Check image tag for version, resolve branches -> commits and redirect to img: @@ -124,22 +125,22 @@ func (s *server) serveKoManifest(w http.ResponseWriter, r *http.Request) { ) if err != nil { s.error.Printf("ERROR (build.NewGo): %s", err) - http.Error(w, err.Error(), http.StatusInternalServerError) + serve.Error(w, serve.ErrInvalid) return } if !g.IsSupportedReference(ip) { s.error.Printf("ERROR (IsSupportedReference): %s", err) - http.Error(w, fmt.Sprintf("%q is not a supported reference", ip), http.StatusBadRequest) + serve.Error(w, serve.ErrInvalid) return } s.info.Printf("ko build %s...", ip) img, err := g.Build(ip) if err != nil { s.error.Printf("ERROR (ko build): %s", err) - http.Error(w, err.Error(), http.StatusInternalServerError) + serve.Error(w, serve.ErrInvalid) return } - pkg.ServeManifest(w, img) + serve.Manifest(w, img) } // Capture up to 99 layers of up to 99.9MB each. @@ -163,8 +164,8 @@ func (s *server) serveRandomManifest(w http.ResponseWriter, r *http.Request) { img, err := random.Image(size, num) if err != nil { s.error.Printf("ERROR (random.Image): %s", err) - http.Error(w, err.Error(), http.StatusInternalServerError) + serve.Error(w, err) return } - pkg.ServeManifest(w, img) + serve.Manifest(w, img) } diff --git a/deploy.sh b/deploy.sh index 1efd0bd..1f2f431 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,11 +1,13 @@ #!/bin/bash -#KO_DOCKER_REPO=gcr.io/kontainme ko publish -P ./cmd/ko && \ -#gcloud --project=kontainme beta run deploy app \ -# --image=gcr.io/kontainme/github.com/imjasonh/kontain.me/cmd/ko \ -# --memory=2Gi \ -# --concurrency=1 \ -# --region=us-central1 +set -euxo pipefail + +KO_DOCKER_REPO=gcr.io/kontainme ko publish -P ./cmd/ko && \ +gcloud --project=kontainme beta run deploy app \ + --image=gcr.io/kontainme/github.com/imjasonh/kontain.me/cmd/ko \ + --memory=2Gi \ + --concurrency=1 \ + --region=us-central1 KO_DOCKER_REPO=gcr.io/kontainme ko publish -P ./cmd/buildpack && \ gcloud --project=kontainme beta run deploy buildpack \ diff --git a/pkg/run/run.go b/pkg/run/run.go new file mode 100644 index 0000000..0816fdb --- /dev/null +++ b/pkg/run/run.go @@ -0,0 +1,13 @@ +package run + +import ( + "io" + "os/exec" +) + +func Do(stdout io.Writer, command string) error { + cmd := exec.Command("/bin/sh", "-c", "set -ex && "+command) + cmd.Stdout = stdout + cmd.Stderr = stdout + return cmd.Run() +} diff --git a/pkg/serve/error.go b/pkg/serve/error.go new file mode 100644 index 0000000..012c649 --- /dev/null +++ b/pkg/serve/error.go @@ -0,0 +1,43 @@ +package serve + +import ( + "encoding/json" + "errors" + "net/http" +) + +var ( + ErrNotFound = errors.New("repository or commit not found") + ErrInvalid = errors.New("requested manifest is invalid") +) + +func Error(w http.ResponseWriter, err error) { + code := "INTERNAL_ERROR" + httpCode := http.StatusInternalServerError + + if err == ErrNotFound { + code = "MANIFEST_UNKNOWN" + httpCode = http.StatusNotFound + } else if err == ErrInvalid { + code = "NAME_INVALID" + httpCode = http.StatusBadRequest + } + + http.Error(w, "", httpCode) + json.NewEncoder(w).Encode(&resp{ + Errors: []e{{ + Code: code, + Message: err.Error(), + }}, + }) +} + +type resp struct { + Errors []e `json:"errors"` +} + +type e struct { + Code string `json:"code"` + Message string `json:"message"` + Reason string `json:"reason,omitempty"` +} diff --git a/pkg/internal.go b/pkg/serve/serve.go similarity index 92% rename from pkg/internal.go rename to pkg/serve/serve.go index b14c7be..cb3cc86 100644 --- a/pkg/internal.go +++ b/pkg/serve/serve.go @@ -1,4 +1,4 @@ -package pkg +package serve import ( "bytes" @@ -8,7 +8,6 @@ import ( "io/ioutil" "log" "net/http" - "os/exec" "strings" "cloud.google.com/go/storage" @@ -18,14 +17,7 @@ import ( const bucket = "kontainme" -func Run(stdout io.Writer, command string) error { - cmd := exec.Command("/bin/sh", "-c", command) - cmd.Stdout = stdout - cmd.Stderr = stdout - return cmd.Run() -} - -func ServeBlob(w http.ResponseWriter, r *http.Request) { +func Blob(w http.ResponseWriter, r *http.Request) { // Extract requested blob digest and redirect to serve it from GCS. // If it doesn't exist, this will return 404. parts := strings.Split(r.URL.Path, "/") @@ -69,7 +61,7 @@ func writeBlob(h v1.Hash, rc io.ReadCloser) error { // ServeManifest writes config and layer blobs for the image, then serves the // manifest contents pointing to those blobs. -func ServeManifest(w http.ResponseWriter, img v1.Image) { +func Manifest(w http.ResponseWriter, img v1.Image) { // Write config blob for later serving. ch, err := img.ConfigName() if err != nil {