Skip to content

Commit

Permalink
Merge pull request agola-io#67 from sgotti/configstore_maintenance_ex…
Browse files Browse the repository at this point in the history
…port_import

configstore: maintenance/export/import
  • Loading branch information
sgotti authored Jul 29, 2019
2 parents ef4c87c + 8f4f0ad commit 60b44de
Show file tree
Hide file tree
Showing 7 changed files with 631 additions and 31 deletions.
23 changes: 16 additions & 7 deletions internal/services/configstore/action/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,30 @@ package action

import (
"agola.io/agola/internal/datamanager"
"agola.io/agola/internal/etcd"
"agola.io/agola/internal/services/configstore/readdb"

"go.uber.org/zap"
)

type ActionHandler struct {
log *zap.SugaredLogger
readDB *readdb.ReadDB
dm *datamanager.DataManager
log *zap.SugaredLogger
readDB *readdb.ReadDB
dm *datamanager.DataManager
e *etcd.Store
maintenanceMode bool
}

func NewActionHandler(logger *zap.Logger, readDB *readdb.ReadDB, dm *datamanager.DataManager) *ActionHandler {
func NewActionHandler(logger *zap.Logger, readDB *readdb.ReadDB, dm *datamanager.DataManager, e *etcd.Store) *ActionHandler {
return &ActionHandler{
log: logger.Sugar(),
readDB: readDB,
dm: dm,
log: logger.Sugar(),
readDB: readDB,
dm: dm,
e: e,
maintenanceMode: false,
}
}

func (h *ActionHandler) SetMaintenanceMode(maintenanceMode bool) {
h.maintenanceMode = maintenanceMode
}
73 changes: 73 additions & 0 deletions internal/services/configstore/action/maintenance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2019 Sorint.lab
//
// 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.

package action

import (
"context"
"io"

"agola.io/agola/internal/etcd"
"agola.io/agola/internal/services/configstore/common"
"agola.io/agola/internal/util"

errors "golang.org/x/xerrors"
)

func (h *ActionHandler) MaintenanceMode(ctx context.Context, enable bool) error {
resp, err := h.e.Get(ctx, common.EtcdMaintenanceKey, 0)
if err != nil && err != etcd.ErrKeyNotFound {
return err
}

if enable && len(resp.Kvs) > 0 {
return util.NewErrBadRequest(errors.Errorf("maintenance mode already enabled"))
}
if !enable && len(resp.Kvs) == 0 {
return util.NewErrBadRequest(errors.Errorf("maintenance mode already disabled"))
}

if enable {
txResp, err := h.e.AtomicPut(ctx, common.EtcdMaintenanceKey, []byte{}, 0, nil)
if err != nil {
return err
}
if !txResp.Succeeded {
return errors.Errorf("failed to create maintenance mode key due to concurrent update")
}
}

if !enable {
txResp, err := h.e.AtomicDelete(ctx, common.EtcdMaintenanceKey, resp.Kvs[0].ModRevision)
if err != nil {
return err
}
if !txResp.Succeeded {
return errors.Errorf("failed to delete maintenance mode key due to concurrent update")
}
}

return nil
}

func (h *ActionHandler) Export(ctx context.Context, w io.Writer) error {
return h.dm.Export(ctx, w)
}

func (h *ActionHandler) Import(ctx context.Context, r io.Reader) error {
if !h.maintenanceMode {
return util.NewErrBadRequest(errors.Errorf("not in maintenance mode"))
}
return h.dm.Import(ctx, r)
}
107 changes: 107 additions & 0 deletions internal/services/configstore/api/maintenance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright 2019 Sorint.lab
//
// 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.

package api

import (
"net/http"

"agola.io/agola/internal/etcd"
"agola.io/agola/internal/services/configstore/action"

"go.uber.org/zap"
)

type MaintenanceModeHandler struct {
log *zap.SugaredLogger
ah *action.ActionHandler
e *etcd.Store
}

func NewMaintenanceModeHandler(logger *zap.Logger, ah *action.ActionHandler, e *etcd.Store) *MaintenanceModeHandler {
return &MaintenanceModeHandler{log: logger.Sugar(), ah: ah, e: e}
}

func (h *MaintenanceModeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

enable := false
switch r.Method {
case "PUT":
enable = true
case "DELETE":
enable = false
}

err := h.ah.MaintenanceMode(ctx, enable)
if err != nil {
h.log.Errorf("err: %+v", err)
httpError(w, err)
return
}

if err := httpResponse(w, http.StatusOK, nil); err != nil {
h.log.Errorf("err: %+v", err)
}

}

type ExportHandler struct {
log *zap.SugaredLogger
ah *action.ActionHandler
}

func NewExportHandler(logger *zap.Logger, ah *action.ActionHandler) *ExportHandler {
return &ExportHandler{log: logger.Sugar(), ah: ah}
}

func (h *ExportHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

err := h.ah.Export(ctx, w)
if err != nil {
h.log.Errorf("err: %+v", err)
// since we already answered with a 200 we cannot return another error code
// So abort the connection and the client will detect the missing ending chunk
// and consider this an error
//
// this is the way to force close a request without logging the panic
panic(http.ErrAbortHandler)
}
}

type ImportHandler struct {
log *zap.SugaredLogger
ah *action.ActionHandler
}

func NewImportHandler(logger *zap.Logger, ah *action.ActionHandler) *ImportHandler {
return &ImportHandler{log: logger.Sugar(), ah: ah}
}

func (h *ImportHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

err := h.ah.Import(ctx, r.Body)
if err != nil {
h.log.Errorf("err: %+v", err)
httpError(w, err)
return
}

if err := httpResponse(w, http.StatusOK, nil); err != nil {
h.log.Errorf("err: %+v", err)
}

}
4 changes: 4 additions & 0 deletions internal/services/configstore/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import (
uuid "github.com/satori/go.uuid"
)

const (
EtcdMaintenanceKey = "maintenance"
)

type RefType int

const (
Expand Down
Loading

0 comments on commit 60b44de

Please # to comment.