diff --git a/server/handler.go b/server/handler.go index 591d7570..c2fe1246 100644 --- a/server/handler.go +++ b/server/handler.go @@ -7,6 +7,7 @@ import ( "math" "math/rand" "net/http" + "path" "regexp" "sort" "strconv" @@ -45,6 +46,71 @@ var ( slackFloodMutex = sync.Mutex{} ) +func (s *Server) downloadSpecificArtifact(c echo.Context) error { + if provider := c.Param("provider"); provider != "circleci" { + return fmt.Errorf("unsupported provider %q", provider) + } + jobName := c.Param("jobname") + filename := c.Param("filename") + sha := c.Param("sha") + + shaFound := false + jobFound := false + availableFilenames := []string{} + for buildID, build := range s.cache.builds { + if build.VcsRevision != sha { + continue + } + shaFound = true + if build.BuildParameters["CIRCLE_JOB"] != jobName { + continue + } + jobFound = true + artifacts, err := s.circleClient.GetArtifacts(fmt.Sprintf("%d", buildID), true) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + for _, artifact := range artifacts { + base := path.Base(artifact.Path) + availableFilenames = append(availableFilenames, base) + if artifact.Path == filename || base == filename { + rc, err := s.circleClient.GetArtifact(artifact) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + contentType := "application/octet-stream" + switch path.Ext(base) { + case ".dmg": + contentType = "application/x-apple-diskimage" + case ".ipa": + contentType = "application/octet-stream" + case ".apk": + contentType = "application/vnd.android.package-archive" + case ".jar": + contentType = "application/java-archive" + case ".txt": + contentType = "text/plain" + case ".json": + contentType = "application/json" + case ".zip": + contentType = "application/zip" + } + // proxy + c.Response().Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=%s", base)) + // FIXME: set Content-Length + return c.Stream(http.StatusOK, contentType, rc) + } + } + } + if !shaFound { + return echo.NewHTTPError(http.StatusNotFound, "no such sha") + } + if !jobFound { + return echo.NewHTTPError(http.StatusNotFound, "no such job") + } + return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("no such artifact filename (available options: %s)", strings.Join(availableFilenames, ", "))) +} + func (s *Server) Build(c echo.Context) error { id := c.Param("build_id") ret, err := s.circleClient.Build(id) diff --git a/server/server.go b/server/server.go index 2c76d2c7..cf5f3354 100644 --- a/server/server.go +++ b/server/server.go @@ -258,6 +258,7 @@ func NewServer(cfg *ServerConfig) (*Server, error) { tokenPaths := regexp.MustCompile("^/auth/ipa/build/.+$|^/auth/dmg/build/.+$|^/auth/apk/build/.+$|^/auth/itms/release/.+$") auth.Use(s.tokenMiddleware(tokenPaths)) + auth.GET("/artifact/:sha/:provider/:jobname/:filename", s.downloadSpecificArtifact) auth.GET("/build/:build_id", s.Build) auth.GET("/builds/*", s.Builds) auth.GET("/artifacts/:build_id", s.Artifacts)