265 lines
8.6 KiB
Go
265 lines
8.6 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"otaupdate/services"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/fiskerinc/cloud-services/pkg/cache"
|
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
|
"github.com/fiskerinc/cloud-services/pkg/utils"
|
|
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
|
|
"github.com/fiskerinc/cloud-services/pkg/validator"
|
|
"github.com/julienschmidt/httprouter"
|
|
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
|
)
|
|
|
|
type ManifestMigrateVersion struct {
|
|
Version int
|
|
}
|
|
|
|
var ERR_MANIFEST_MIGRATE_BAD_VERSION = errors.New("received version not recognized")
|
|
|
|
// TargetURLS is multiple as stage pushes to Prod and Euro Prod
|
|
var apiToken string = envtool.GetEnv("MANIFEST_MIGRATE_TOKEN", "MISSING API TOKEN")
|
|
var targetURLS []string = strings.Split(envtool.GetEnv("MANIFEST_MIGRATE_URLS", "MISSING_TARGET_URL"), ",")
|
|
|
|
// So we are going to take the information from our lower env and push it into a higher one.
|
|
// Need to decide how we want to get the information from the lower level to this higher level
|
|
// HandleUpdateManifestMigrate godoc
|
|
// @Summary Push update manifest from this environment to a higher one
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param Authorization header string false "Bearer <ID token>"
|
|
// @Param Api-Key header string false "<API token>"
|
|
// @Param manifest_id path string true "Manifest ID"
|
|
// @Success 200 {object} common.JSONMessage
|
|
// @Failure 400 {object} common.JSONError "Bad Request"
|
|
// @Failure 401 {object} common.JSONError "Unauthorized"
|
|
// @Failure 503 {object} common.JSONError "Service unavailable"
|
|
// @Router /manifestmigrate/{manifest_id} [post]
|
|
func HandleUpdateManifestMigrate(w http.ResponseWriter, r *http.Request) {
|
|
params := httprouter.ParamsFromContext(r.Context())
|
|
manifestIdString := params.ByName("manifest_id")
|
|
|
|
// Gather the right information
|
|
manifestId, err := strconv.Atoi(manifestIdString)
|
|
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
|
return
|
|
}
|
|
var manifest common.UpdateManifest
|
|
manifest.ID = int64(manifestId)
|
|
|
|
err = validator.ValidateIDField(manifest.ID)
|
|
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
|
return
|
|
}
|
|
|
|
um := services.GetDB().GetUpdateManifests()
|
|
err = um.Load(&manifest)
|
|
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable, loggerdataresp.PostgresNoRowsErrorCheck) {
|
|
return
|
|
}
|
|
|
|
manifest.SUMS = ""
|
|
manifest.Env = ""
|
|
|
|
manifest.MigratePrepareHardwareVersion()
|
|
|
|
fileKeys, err := getFileKeys(manifest)
|
|
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable, loggerdataresp.PostgresNoRowsErrorCheck) {
|
|
return
|
|
}
|
|
|
|
sendStruct := ManifestMigrateBody{}
|
|
sendStruct.MigratedManifest = manifest
|
|
sendStruct.FileKeys = fileKeys
|
|
|
|
// Send message to the other API's
|
|
sendBody, err := PrepareMigrateBodyForSending(&sendStruct)
|
|
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
|
return
|
|
}
|
|
|
|
for index, targetURL := range targetURLS {
|
|
turl, err := url.JoinPath(targetURL, "manifestmigrate")
|
|
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
|
return
|
|
}
|
|
req, err := http.NewRequest("POST", turl, bytes.NewBuffer(sendBody))
|
|
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
|
return
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Add("Api-Key", apiToken)
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
|
deleteBecauseFailedMigrate(targetURLS[:index], int(manifest.ID))
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
b, _ := io.ReadAll(resp.Body)
|
|
// Forgot to check the status code
|
|
if resp.StatusCode >= 300 {
|
|
logger.Error().Msgf("Failed to send manifest migrate: %s \n Status: %s\n Resp Body: %s\n", string(sendBody), resp.Status, string(b))
|
|
var response common.JSONError
|
|
err := json.Unmarshal(b, &response)
|
|
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
|
return
|
|
}
|
|
utils.RespJSON(w, http.StatusBadRequest, response)
|
|
return
|
|
}
|
|
}
|
|
utils.RespJSON(w, http.StatusOK, common.JSONMessage{
|
|
Message: "Migration Sent",
|
|
})
|
|
}
|
|
|
|
// If we fail to upload a manifest to one of the urls, delete from the other environments we are uploading to
|
|
func deleteBecauseFailedMigrate(urls []string, manifestID int) {
|
|
for _, targetURL := range urls {
|
|
turl, err := url.JoinPath(targetURL, "manifestraw")
|
|
if err != nil {
|
|
logger.Err(err).Msgf("Failed to delete manifest %d from %s after failing an upload", manifestID, targetURL)
|
|
continue
|
|
}
|
|
turl += fmt.Sprintf("?id=%d", manifestID)
|
|
req, err := http.NewRequest("DELETE", turl, nil)
|
|
if err != nil {
|
|
logger.Err(err).Msgf("Failed to delete manifest %d from %s after failing an upload", manifestID, targetURL)
|
|
continue
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Add("Api-Key", apiToken)
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
logger.Err(err).Msgf("Failed to delete manifest %d from %s after failing an upload %s", manifestID, targetURL, resp.Status)
|
|
continue
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode >= 300 {
|
|
if err != nil {
|
|
logger.Err(err).Msgf("Failed to delete manifest %d from %s after failing an upload %s", manifestID, targetURL, resp.Status)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func getFileKeys(manifest common.UpdateManifest) ([]common.FileKeyResponse, error) {
|
|
conn := services.RedisClientPool().GetFromPool()
|
|
dbFK := services.GetDB().GetFileKeys()
|
|
defer conn.Close()
|
|
fileIDs := make([]string, 0)
|
|
for _, ecu := range manifest.ECUs {
|
|
for _, file := range ecu.Files {
|
|
fileIDs = append(fileIDs, file.FileID)
|
|
}
|
|
}
|
|
return cache.RetrieveFileEncryptionParams(conn, dbFK, fileIDs)
|
|
}
|
|
|
|
// In case we need a completely different struct, we will just return the bytes
|
|
func PrepareMigrateBodyForSending(manifestMigrate *ManifestMigrateBody) (jsonBody []byte, err error) {
|
|
v, err := getVersionToSend()
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = transformMigrateBody(manifestMigrate, v)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return json.Marshal(manifestMigrate)
|
|
}
|
|
|
|
// Get the version that the env we are pushing to is going to run, then we can transform is needed or not send at all
|
|
// If we do not get a version, it defaults to 0
|
|
func getVersionToSend() (version int, err error) {
|
|
version = -1
|
|
if len(targetURLS) == 0 {
|
|
return version, errors.New("missing target url")
|
|
}
|
|
targetURL := targetURLS[0]
|
|
// The eu prod and normal prod should always be the same version, so just going to act like they are
|
|
turl, err := url.JoinPath(targetURL, "manifestmigrate-version")
|
|
if err != nil {
|
|
return
|
|
}
|
|
req, err := http.NewRequest("GET", turl, nil)
|
|
if err != nil {
|
|
return
|
|
}
|
|
req.Header.Add("Api-Key", apiToken)
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
// If they don't have this route, we can assume they are on version 0
|
|
if resp.StatusCode >= 300 {
|
|
logger.Warn().Msgf("manifest migrate version returned status code %s", resp.Status)
|
|
version = 0
|
|
return
|
|
}
|
|
var mmv ManifestMigrateVersion
|
|
err = json.NewDecoder(resp.Body).Decode(&mmv)
|
|
if err != nil {
|
|
return
|
|
}
|
|
version = mmv.Version
|
|
|
|
return
|
|
}
|
|
|
|
const MIGRATION_VERSION = 1
|
|
|
|
// If we do not get a version, it defaults to 0
|
|
// If we cannot send the manifest due to some major change, then return an error
|
|
func transformMigrateBody(manifestMigrate *ManifestMigrateBody, version int) (err error) {
|
|
// We have to go from our version down to their version
|
|
// EX we are on version 5, they are on version 3
|
|
// So do the conversion of 5 -> 4, then 4 -> 3
|
|
|
|
// For each version that is added on, add to this switch statement
|
|
// This switch statement is more of a way to organize the flow, can probably be improved
|
|
switch MIGRATION_VERSION {
|
|
case 1: // The conversion needed to go from VERSION 1 to VERSION 0
|
|
if version == 1 { // If we are at the desired version, then we can break
|
|
break
|
|
}
|
|
swapECUInstallPriority(manifestMigrate)
|
|
fallthrough
|
|
case 0:
|
|
// THis is just an example row to show the fallthrough from case of 1 -> 0
|
|
if version == 0 {
|
|
break
|
|
}
|
|
default:
|
|
err = ERR_MANIFEST_MIGRATE_BAD_VERSION
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func swapECUInstallPriority(manifestMigrate *ManifestMigrateBody) {
|
|
ecuList := manifestMigrate.MigratedManifest.ECUs
|
|
// Order the array so we can begin to swap easily
|
|
sort.Slice(ecuList, func(i, j int) bool { return ecuList[i].InstallPriority < ecuList[j].InstallPriority })
|
|
rightIndex := len(ecuList) - 1
|
|
for leftIndex := 0; leftIndex < rightIndex; leftIndex++ {
|
|
ecuList[leftIndex].InstallPriority, ecuList[rightIndex].InstallPriority = ecuList[rightIndex].InstallPriority, ecuList[leftIndex].InstallPriority
|
|
rightIndex--
|
|
}
|
|
}
|