diff --git a/CHANGELOG.md b/CHANGELOG.md index bd4c3d5b1..b37b0ac79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - endpoint to trigger new build for default branch, by [@bradrydzewski](https://github.com/bradrydzewski). [#2679](https://github.com/drone/drone/issues/2679). - endpoint to trigger new build for branch, by [@bradrydzewski](https://github.com/bradrydzewski). [#2679](https://github.com/drone/drone/issues/2679). - endpoint to trigger new build for branch and sha, by [@bradrydzewski](https://github.com/bradrydzewski). [#2679](https://github.com/drone/drone/issues/2679). +- enable optional prometheus metrics guest access, by [@janberktold](https://github.com/janberktold) + +### Fixed + +- retrieve latest build by branch, by [@tboerger](https://github.com/tboerger). ### Fixed @@ -21,8 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - specify a user for the pipeline step, by [@bradrydzewski](https://github.com/bradrydzewski). [#2651](https://github.com/drone/drone/issues/2651). - support for Gitea oauth2, by [@techknowlogick](https://github.com/techknowlogick). [#2622](https://github.com/drone/drone/pull/2622). - ping the docker daemon before starting the agent, by [@bradrydzewski](https://github.com/bradrydzewski). [#2495](https://github.com/drone/drone/issues/2495). -- support for Cron job name in Yaml trigger block, by [@bradrydzewski](https://github.com/bradrydzewski). [#2628](https://github.com/drone/drone/issues/2628). -- support for Cron job name in Yaml when block, by [@bradrydzewski](https://github.com/bradrydzewski). [#2628](https://github.com/drone/drone/issues/2628). +- support for Cron job name in Yaml trigger block, by [@bradrydzewski](https://github.com/bradrydzewski). [#2628](https://github.com/drone/drone/issues/2628). +- support for Cron job name in Yaml when block, by [@bradrydzewski](https://github.com/bradrydzewski). [#2628](https://github.com/drone/drone/issues/2628). - sqlite username column changed to case-insensitive, by [@bradrydzewski](https://github.com/bradrydzewski). - endpoint to purge repository from database, by [@bradrydzewski](https://github.com/bradrydzewski). - support for per-organization secrets, by [@bradrydzewski](https://github.com/bradrydzewski). diff --git a/cmd/drone-agent/main.go b/cmd/drone-agent/main.go index 3af558474..a33457e0a 100644 --- a/cmd/drone-agent/main.go +++ b/cmd/drone-agent/main.go @@ -8,6 +8,7 @@ package main import ( "context" + "flag" "time" "github.com/drone/drone-runtime/engine/docker" @@ -20,13 +21,20 @@ import ( "github.com/sirupsen/logrus" + "github.com/joho/godotenv" _ "github.com/joho/godotenv/autoload" ) func main() { + var envfile string + flag.StringVar(&envfile, "env-file", ".env", "Read in a file of environment variables") + flag.Parse() + + godotenv.Load(envfile) config, err := config.Environ() if err != nil { - logrus.WithError(err).Fatalln("invalid configuration") + logger := logrus.WithError(err) + logger.Fatalln("invalid configuration") } initLogging(config) diff --git a/cmd/drone-server/config/config.go b/cmd/drone-server/config/config.go index e77a61dfc..3cdd4bfc6 100644 --- a/cmd/drone-server/config/config.go +++ b/cmd/drone-server/config/config.go @@ -46,17 +46,17 @@ type ( Config struct { License string `envconfig:"DRONE_LICENSE"` - Authn Authentication - Agent Agent - Cron Cron - Cloning Cloning - Database Database - Datadog Datadog - Docker Docker - HTTP HTTP - Jsonnet Jsonnet - Logging Logging - // Prometheus Prometheus + Authn Authentication + Agent Agent + Cron Cron + Cloning Cloning + Database Database + Datadog Datadog + Docker Docker + HTTP HTTP + Jsonnet Jsonnet + Logging Logging + Prometheus Prometheus Proxy Proxy Registration Registration Registries Registries @@ -162,6 +162,11 @@ type ( Text bool `envconfig:"DRONE_LOGS_TEXT"` } + // Prometheus provides the prometheus configuration. + Prometheus struct { + EnableAnonymousAccess bool `envconfig:"DRONE_PROMETHEUS_ANONYMOUS_ACCESS" default:"false"` + } + // Repository provides the repository configuration. Repository struct { Filter []string `envconfig:"DRONE_REPOSITORY_FILTER"` diff --git a/cmd/drone-server/inject_server.go b/cmd/drone-server/inject_server.go index 26e6b026a..eaedbda3d 100644 --- a/cmd/drone-server/inject_server.go +++ b/cmd/drone-server/inject_server.go @@ -18,6 +18,7 @@ import ( "net/http" "github.com/drone/drone/cmd/drone-server/config" + "github.com/drone/drone/core" "github.com/drone/drone/handler/api" "github.com/drone/drone/handler/web" "github.com/drone/drone/metric" @@ -31,12 +32,19 @@ import ( "github.com/unrolled/secure" ) +type ( + healthzHandler http.Handler + metricsHandler http.Handler + rpcHandlerV1 http.Handler + rpcHandlerV2 http.Handler +) + // wire set for loading the server. var serverSet = wire.NewSet( manager.New, - metric.NewServer, api.New, web.New, + provideMetric, provideRouter, provideRPC, provideRPC2, @@ -46,26 +54,34 @@ var serverSet = wire.NewSet( // provideRouter is a Wire provider function that returns a // router that is serves the provided handlers. -func provideRouter(api api.Server, web web.Server, rpc http.Handler, rpcv2 rpc2.Server, metrics *metric.Server) *chi.Mux { +func provideRouter(api api.Server, web web.Server, rpcv1 rpcHandlerV1, rpcv2 rpcHandlerV2, metrics *metric.Server) *chi.Mux { r := chi.NewRouter() r.Mount("/metrics", metrics) r.Mount("/api", api.Handler()) r.Mount("/rpc/v2", rpcv2) - r.Mount("/rpc", rpc) + r.Mount("/rpc", rpcv1) r.Mount("/", web.Handler()) return r } +// provideMetric is a Wire provider function that returns the +// metrics server exposing metrics in prometheus format. +func provideMetric(session core.Session, config config.Config) *metric.Server { + return metric.NewServer(session, config.Prometheus.EnableAnonymousAccess) +} + // provideRPC is a Wire provider function that returns an rpc // handler that exposes the build manager to a remote agent. -func provideRPC(m manager.BuildManager, config config.Config) http.Handler { - return rpc.NewServer(m, config.RPC.Secret) +func provideRPC(m manager.BuildManager, config config.Config) rpcHandlerV1 { + v := rpc.NewServer(m, config.RPC.Secret) + return rpcHandlerV1(v) } // provideRPC2 is a Wire provider function that returns an rpc // handler that exposes the build manager to a remote agent. -func provideRPC2(m manager.BuildManager, config config.Config) rpc2.Server { - return rpc2.NewServer(m, config.RPC.Secret) +func provideRPC2(m manager.BuildManager, config config.Config) rpcHandlerV2 { + v := rpc2.NewServer(m, config.RPC.Secret) + return rpcHandlerV2(v) } // provideServer is a Wire provider function that returns an diff --git a/cmd/drone-server/wire_gen.go b/cmd/drone-server/wire_gen.go index b11d75684..8d92c8ffc 100644 --- a/cmd/drone-server/wire_gen.go +++ b/cmd/drone-server/wire_gen.go @@ -10,7 +10,6 @@ import ( "github.com/drone/drone/handler/api" "github.com/drone/drone/handler/web" "github.com/drone/drone/livelog" - "github.com/drone/drone/metric" "github.com/drone/drone/operator/manager" "github.com/drone/drone/pubsub" "github.com/drone/drone/service/commit" @@ -94,7 +93,7 @@ func InitializeApplication(config2 config.Config) (application, error) { webServer := web.New(admissionService, buildStore, client, hookParser, coreLicense, licenseService, middleware, repositoryStore, session, syncer, triggerer, userStore, userService, webhookSender, options, system) handler := provideRPC(buildManager, config2) rpc2Server := provideRPC2(buildManager, config2) - metricServer := metric.NewServer(session) + metricServer := provideMetric(session, config2) mux := provideRouter(server, webServer, handler, rpc2Server, metricServer) serverServer := provideServer(mux, config2) mainApplication := newApplication(cronScheduler, datadog, runner, serverServer, userStore) diff --git a/handler/api/repos/builds/latest.go b/handler/api/repos/builds/latest.go index 886cbac1c..a15be1308 100644 --- a/handler/api/repos/builds/latest.go +++ b/handler/api/repos/builds/latest.go @@ -36,6 +36,7 @@ func HandleLast( namespace = chi.URLParam(r, "owner") name = chi.URLParam(r, "name") ref = r.FormValue("ref") + branch = r.FormValue("branch") ) repo, err := repos.FindName(r.Context(), namespace, name) if err != nil { @@ -45,6 +46,9 @@ func HandleLast( if ref == "" { ref = fmt.Sprintf("refs/heads/%s", repo.Branch) } + if branch != "" { + ref = fmt.Sprintf("refs/heads/%s", branch) + } build, err := builds.FindRef(r.Context(), repo.ID, ref) if err != nil { render.NotFound(w, err) diff --git a/metric/handler.go b/metric/handler.go index 66c5ef7fc..724e3b42e 100644 --- a/metric/handler.go +++ b/metric/handler.go @@ -24,15 +24,17 @@ var errAccessDenied = errors.New("Access denied") // Server is an http Metrics server. type Server struct { - metrics http.Handler - session core.Session + metrics http.Handler + session core.Session + anonymous bool } // NewServer returns a new metrics server. -func NewServer(session core.Session) *Server { +func NewServer(session core.Session, anonymous bool) *Server { return &Server{ - metrics: promhttp.Handler(), - session: session, + metrics: promhttp.Handler(), + session: session, + anonymous: anonymous, } } @@ -41,9 +43,9 @@ func NewServer(session core.Session) *Server { func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { user, _ := s.session.Get(r) switch { - case user == nil: + case !s.anonymous && user == nil: http.Error(w, errInvalidToken.Error(), 401) - case !user.Admin && !user.Machine: + case !s.anonymous && !user.Admin && !user.Machine: http.Error(w, errAccessDenied.Error(), 403) default: s.metrics.ServeHTTP(w, r) diff --git a/metric/handler_oss.go b/metric/handler_oss.go index a20a4e695..253f3c289 100644 --- a/metric/handler_oss.go +++ b/metric/handler_oss.go @@ -27,7 +27,7 @@ type Server struct { } // NewServer returns a new metrics server. -func NewServer(session core.Session) *Server { +func NewServer(session core.Session, anonymous bool) *Server { return new(Server) } diff --git a/metric/handler_test.go b/metric/handler_test.go index 27892aaf1..2931e135b 100644 --- a/metric/handler_test.go +++ b/metric/handler_test.go @@ -26,7 +26,7 @@ func TestHandleMetrics(t *testing.T) { session := mock.NewMockSession(controller) session.EXPECT().Get(r).Return(mockUser, nil) - NewServer(session).ServeHTTP(w, r) + NewServer(session, false).ServeHTTP(w, r) if got, want := w.Code, 200; got != want { t.Errorf("Want status code %d, got %d", want, got) } @@ -46,13 +46,30 @@ func TestHandleMetrics_NoSession(t *testing.T) { session := mock.NewMockSession(controller) session.EXPECT().Get(r).Return(nil, nil) - NewServer(session).ServeHTTP(w, r) + NewServer(session, false).ServeHTTP(w, r) if got, want := w.Code, 401; got != want { t.Errorf("Want status code %d, got %d", want, got) } } +func TestHandleMetrics_NoSessionButAnonymousAccessEnabled(t *testing.T) { + controller := gomock.NewController(t) + defer controller.Finish() + + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/", nil) + + session := mock.NewMockSession(controller) + session.EXPECT().Get(r).Return(nil, nil) + + NewServer(session, true).ServeHTTP(w, r) + + if got, want := w.Code, 200; got != want { + t.Errorf("Want status code %d, got %d", want, got) + } +} + func TestHandleMetrics_AccessDenied(t *testing.T) { controller := gomock.NewController(t) defer controller.Finish() @@ -64,7 +81,7 @@ func TestHandleMetrics_AccessDenied(t *testing.T) { session := mock.NewMockSession(controller) session.EXPECT().Get(r).Return(mockUser, nil) - NewServer(session).ServeHTTP(w, r) + NewServer(session, false).ServeHTTP(w, r) if got, want := w.Code, 403; got != want { t.Errorf("Want status code %d, got %d", want, got) } diff --git a/operator/manager/rpc/client.go b/operator/manager/rpc/client.go index 669cfa2c9..73a39d042 100644 --- a/operator/manager/rpc/client.go +++ b/operator/manager/rpc/client.go @@ -261,7 +261,7 @@ func (s *Client) send(ctx context.Context, path string, in, out interface{}) err // Check the response for a 204 no content. This indicates // the response body is empty and should be discarded. - if res.StatusCode == 204 { + if res.StatusCode == 204 || out == nil { return nil } diff --git a/operator/manager/rpc/client_test.go b/operator/manager/rpc/client_test.go index da73ca20b..3cb4a468d 100644 --- a/operator/manager/rpc/client_test.go +++ b/operator/manager/rpc/client_test.go @@ -69,7 +69,7 @@ func TestAccept(t *testing.T) { client := NewClient("http://drone.company.com", "correct-horse-battery-staple") gock.InterceptClient(client.client.HTTPClient) - err := client.Accept(noContext, 1, "localhost") + _, err := client.Accept(noContext, 1, "localhost") if err != nil { t.Error(err) } diff --git a/operator/manager/rpc2/server.go b/operator/manager/rpc2/server.go index 2371e7bb9..d34fccbb7 100644 --- a/operator/manager/rpc2/server.go +++ b/operator/manager/rpc2/server.go @@ -4,18 +4,6 @@ // +build !oss -/* - -/stage POST (request) -/stage/{stage}?machine= POST (accept, details) -/stage/{stage} PUT (beforeAll, afterAll) -/stage/{stage}/steps/{step} PUT (before, after) -/build/{build}/watch POST (watch) -/stage/{stage}/logs/batch POST (batch) -/stage/{stage}/logs/upload POST (upload) - -*/ - package rpc2 import ( @@ -55,7 +43,11 @@ func NewServer(manager manager.BuildManager, secret string) Server { func authorization(token string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if token == r.Header.Get("X-Drone-Token") { + // prevents system administrators from accidentally + // exposing drone without credentials. + if token == "" { + w.WriteHeader(403) + } else if token == r.Header.Get("X-Drone-Token") { next.ServeHTTP(w, r) } else { w.WriteHeader(401) diff --git a/operator/manager/rpc2/server_oss.go b/operator/manager/rpc2/server_oss.go new file mode 100644 index 000000000..6a225f50a --- /dev/null +++ b/operator/manager/rpc2/server_oss.go @@ -0,0 +1,33 @@ +// Copyright 2019 Drone IO, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build oss + +package rpc2 + +import ( + "net/http" + + "github.com/drone/drone/operator/manager" +) + +// Server wraps the chi Router in a custom type for wire +// injection purposes. +type Server http.Handler + +// NewServer returns a new rpc server that enables remote +// interaction with the build controller using the http transport. +func NewServer(manager manager.BuildManager, secret string) Server { + return Server(http.NotFoundHandler()) +} diff --git a/operator/runner/runner.go b/operator/runner/runner.go index 7ddb1444a..123ce24fb 100644 --- a/operator/runner/runner.go +++ b/operator/runner/runner.go @@ -579,12 +579,13 @@ func (r *Runner) poll(ctx context.Context) error { if err == db.ErrOptimisticLock { return nil } else if err != nil { - logger.WithFields( - logrus.Fields{ - "stage-id": p.ID, - "build-id": p.BuildID, - "repo-id": p.RepoID, - }).Warnln("runner: cannot ack stage") + logger.WithError(err). + WithFields( + logrus.Fields{ + "stage-id": p.ID, + "build-id": p.BuildID, + "repo-id": p.RepoID, + }).Warnln("runner: cannot ack stage") return err }