api/internal/handler/migrate/migrate.go (101 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 migrate import ( "encoding/binary" "hash/crc32" "io/ioutil" "net/http" "github.com/gin-gonic/gin" "github.com/shiningrush/droplet/data" "github.com/apisix/manager-api/internal/core/migrate" "github.com/apisix/manager-api/internal/handler" "github.com/apisix/manager-api/internal/log" "github.com/apisix/manager-api/internal/utils/consts" ) const ( exportFileName = "apisix-config.bak" checksumLength = 4 // 4 bytes (uint32) ) type Handler struct{} func NewHandler() (handler.RouteRegister, error) { return &Handler{}, nil } func (h *Handler) ApplyRoute(r *gin.Engine) { r.GET("/apisix/admin/migrate/export", h.ExportConfig) r.POST("/apisix/admin/migrate/import", h.ImportConfig) } type ExportInput struct{} func (h *Handler) ExportConfig(c *gin.Context) { data, err := migrate.Export(c) if err != nil { log.Errorf("Export: %s", err) c.JSON(http.StatusInternalServerError, err) return } // To check file integrity // Add 4 byte(uint32) checksum at the end of file. checksumUint32 := crc32.ChecksumIEEE(data) checksum := make([]byte, checksumLength) binary.BigEndian.PutUint32(checksum, checksumUint32) fileBytes := append(data, checksum...) c.Writer.WriteHeader(http.StatusOK) c.Header("Content-Disposition", "attachment; filename="+exportFileName) c.Header("Content-Type", "application/octet-stream") c.Header("Content-Transfer-Encoding", "binary") _, err = c.Writer.Write([]byte(fileBytes)) if err != nil { log.Errorf("Write: %s", err) } } type ImportOutput struct { ConflictItems *migrate.DataSet } var modeMap = map[string]migrate.ConflictMode{ "return": migrate.ModeReturn, "overwrite": migrate.ModeOverwrite, "skip": migrate.ModeSkip, } func (h *Handler) ImportConfig(c *gin.Context) { paraMode := c.PostForm("mode") mode := migrate.ModeReturn if m, ok := modeMap[paraMode]; ok { mode = m } file, _, err := c.Request.FormFile("file") if err != nil { c.JSON(http.StatusInternalServerError, err) return } content, err := ioutil.ReadAll(file) if err != nil { c.JSON(http.StatusInternalServerError, err) return } // checksum uint32,4 bytes importData := content[:len(content)-4] checksum := binary.BigEndian.Uint32(content[len(content)-4:]) if checksum != crc32.ChecksumIEEE(importData) { c.JSON(http.StatusOK, &data.BaseError{ Code: consts.ErrBadRequest, Message: "Checksum check failure,maybe file broken", }) return } conflictData, err := migrate.Import(c, importData, mode) if err != nil { if err == migrate.ErrConflict { c.JSON(http.StatusOK, &data.BaseError{ Code: consts.ErrBadRequest, Message: "Config conflict", Data: ImportOutput{ConflictItems: conflictData}, }) } else { log.Errorf("Import failed: %s", err) c.JSON(http.StatusOK, &data.BaseError{ Code: consts.ErrBadRequest, Message: err.Error(), Data: ImportOutput{ConflictItems: conflictData}, }) } return } c.JSON(http.StatusOK, &data.Response{ Data: ImportOutput{ConflictItems: conflictData}, }) }