Files
cloud-services/pkg/vehicleconfig/sap.go

212 lines
5.7 KiB
Go

package vehicleconfig
import (
"bytes"
"crypto/tls"
"encoding/base64"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"net/http"
"time"
"github.com/pkg/errors"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/common/staticerrors"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
)
func NewSAPService() SAPServiceInterface {
return &SAPService{
sapUser: envtool.GetEnv("SAP_USER", "REPLACE_ME"),
sapPass: envtool.GetEnv("SAP_PASS", "REPLACE_ME"),
sapURL: envtool.GetEnv("SAP_URL", "REPLACE_ME"),
}
}
type SAPServiceInterface interface {
GetSAPOrder(vin string) (order common.VehicleOrder, err error)
GetECUVersions(vin string) (versions map[string]string, err error)
UpdateECUVersions(vin string, versions map[string]string) (err error)
GetFeatureCodes(vin string) (common.SAPResponse, error)
SubmitResult(vin string, success bool) (err error)
SubmitCarFlashpackVersion(vin string, previousFlashpack string, flashpack string) error
}
// This is currently a mock of the actual calls to the SAP and the ODX parser services
type SAPService struct {
sapUser string
sapPass string
sapURL string
}
type SAPSubmitResultBody struct {
VIN string `json:"vin" validate:"required,vin"`
DateTime string `json:"dateTime"`
Flag string `json:"flag"`
}
type SAPSubmitCarFlashpackVersionBody struct {
VIN string `json:"vin" validate:"required,vin"`
CreatedOn string `json:"createdOn"`
CurrentSWV string `json:"currentSWV"`
PreviousSWV string `json:"previousSWV"`
ModeOfSWU string `json:"modeOfSWU"`
}
// Returns the current vehicle order from SAP
func (cs *SAPService) GetSAPOrder(vin string) (order common.VehicleOrder, err error) {
var result common.OrderUpdated
err = xml.Unmarshal([]byte(vehicleOrder), &result)
if err != nil {
return
}
order = result.VehicleOrder
return
}
// Returns the current ECU versions for a car
func (cs *SAPService) GetECUVersions(vin string) (versions map[string]string, err error) {
versions = map[string]string{}
for _, ecu := range ecus {
versions[ecu] = "10000"
}
return
}
// Submits the updated ECU versions for a car back to SAP
func (cs *SAPService) UpdateECUVersions(vin string, versions map[string]string) (err error) {
return
}
func (cs *SAPService) GetFeatureCodes(vin string) (common.SAPResponse, error) {
headers := http.Header{}
headers.Add("Authorization", cs.createToken())
url := fmt.Sprintf("%s/vehicleSpec?vin=%s", cs.sapURL, vin)
request, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return common.SAPResponse{}, errors.WithStack(err)
}
client := createHttpClient()
request.Header = headers
resp, err := client.Do(request)
if err != nil {
return common.SAPResponse{}, errors.WithStack(err)
}
if resp.StatusCode != http.StatusOK {
return common.SAPResponse{}, errors.WithStack(fmt.Errorf(staticerrors.ErrorSAPFailedCall+" with status: %s", resp.Status))
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return common.SAPResponse{}, errors.WithStack(err)
}
respPayload := common.SAPResponse{}
err = json.Unmarshal(respBody, &respPayload)
if err != nil {
return common.SAPResponse{}, errors.WithStack(err)
}
return respPayload, nil
}
func (cs *SAPService) SubmitResult(vin string, success bool) (err error) {
headers := http.Header{}
headers.Add("Authorization", cs.createToken())
headers.Add("Content-Type", "application/json")
timeNow := time.Now()
formattedTime := timeNow.Format("2006-01-02T15:04:05")
body := &SAPSubmitResultBody{
VIN: vin,
DateTime: formattedTime,
Flag: "not completed",
}
if success {
body.Flag = "completed"
}
bodyJSON, err := json.Marshal(body)
if err != nil {
return errors.WithStack(err)
}
return cs.submit("vehicleSpecAck", bodyJSON, http.StatusOK)
}
func (cs *SAPService) SubmitCarFlashpackVersion(vin string, previousFlashpack string, flashpack string) error {
timeNow := time.Now()
formattedTime := timeNow.Format("2006-01-02T15:04:05.999999Z")
body := &SAPSubmitCarFlashpackVersionBody{
VIN: vin,
CreatedOn: formattedTime,
PreviousSWV: previousFlashpack,
CurrentSWV: flashpack,
ModeOfSWU: "OTA",
}
bodyJSON, err := json.Marshal(body)
if err != nil {
return errors.WithStack(err)
}
return cs.submit("swVersionUpdate", bodyJSON, http.StatusCreated)
}
func (cs *SAPService) submit(endpoint string, bodyJSON []byte, successStatus int) error {
headers := http.Header{}
headers.Add("Authorization", cs.createToken())
headers.Add("Content-Type", "application/json")
url := fmt.Sprintf("%s/%s", cs.sapURL, endpoint)
request, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(bodyJSON))
if err != nil {
return errors.WithStack(err)
}
client := createHttpClient()
request.Header = headers
resp, err := client.Do(request)
if err != nil {
return errors.WithStack(err)
}
if resp.StatusCode != successStatus {
return errors.WithStack(fmt.Errorf(staticerrors.ErrorSAPFailedCall+" with status: %s and payload: %s", resp.Status, string(bodyJSON)))
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return errors.WithStack(err)
}
logger.Info().Msg(fmt.Sprintf("sap service has been notified at %s of car update: %s", endpoint, string(respBody)))
return nil
}
func (cs *SAPService) createToken() string {
token := base64.StdEncoding.EncodeToString([]byte(cs.sapUser + ":" + cs.sapPass))
return fmt.Sprintf("Basic %s", token)
}
func createHttpClient() http.Client {
transport := http.DefaultTransport.(*http.Transport)
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
return http.Client{
Transport: transport,
}
}