199 lines
6.4 KiB
Go
199 lines
6.4 KiB
Go
// -*- Mode: Go; indent-tabs-mode: t -*-
|
|
//
|
|
// Copyright (C) 2017-2018 Canonical Ltd
|
|
// Copyright (C) 2018-2023 IOTech Ltd
|
|
// Copyright (C) 2023 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package provision
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/container"
|
|
"github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/file"
|
|
"github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/interfaces"
|
|
"github.com/edgexfoundry/go-mod-bootstrap/v4/di"
|
|
"github.com/edgexfoundry/go-mod-core-contracts/v4/clients/logger"
|
|
"github.com/edgexfoundry/go-mod-core-contracts/v4/common"
|
|
"github.com/edgexfoundry/go-mod-core-contracts/v4/dtos"
|
|
"github.com/edgexfoundry/go-mod-core-contracts/v4/dtos/requests"
|
|
"github.com/edgexfoundry/go-mod-core-contracts/v4/errors"
|
|
"github.com/edgexfoundry/go-mod-core-contracts/v4/models"
|
|
"github.com/google/uuid"
|
|
"github.com/hashicorp/go-multierror"
|
|
"gopkg.in/yaml.v3"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/edgexfoundry/device-sdk-go/v4/internal/cache"
|
|
"github.com/edgexfoundry/device-sdk-go/v4/internal/container"
|
|
)
|
|
|
|
func LoadDevices(path string, dic *di.Container) errors.EdgeX {
|
|
var addDevicesReq []requests.AddDeviceRequest
|
|
var edgexErr errors.EdgeX
|
|
if path == "" {
|
|
return nil
|
|
}
|
|
|
|
lc := bootstrapContainer.LoggingClientFrom(dic.Get)
|
|
serviceName := container.DeviceServiceFrom(dic.Get).Name
|
|
parsedUrl, err := url.Parse(path)
|
|
if err != nil {
|
|
return errors.NewCommonEdgeX(errors.KindServerError, "failed to parse Devices path as a URI", err)
|
|
}
|
|
if parsedUrl.Scheme == "http" || parsedUrl.Scheme == "https" {
|
|
secretProvider := bootstrapContainer.SecretProviderFrom(dic.Get)
|
|
addDevicesReq, edgexErr = loadDevicesFromURI(path, parsedUrl, serviceName, secretProvider, lc)
|
|
if edgexErr != nil {
|
|
return edgexErr
|
|
}
|
|
} else {
|
|
addDevicesReq, edgexErr = loadDevicesFromFile(path, serviceName, lc)
|
|
if edgexErr != nil {
|
|
return edgexErr
|
|
}
|
|
}
|
|
|
|
if len(addDevicesReq) == 0 {
|
|
return nil
|
|
}
|
|
dc := bootstrapContainer.DeviceClientFrom(dic.Get)
|
|
ctx := context.WithValue(context.Background(), common.CorrelationHeader, uuid.NewString()) //nolint: staticcheck
|
|
responses, edgexErr := dc.Add(ctx, addDevicesReq)
|
|
if edgexErr != nil {
|
|
return edgexErr
|
|
}
|
|
|
|
err = nil
|
|
for _, response := range responses {
|
|
if response.StatusCode != http.StatusCreated {
|
|
if response.StatusCode == http.StatusConflict {
|
|
lc.Warnf("%s. Device may be owned by other Device service instance.", response.Message)
|
|
continue
|
|
}
|
|
|
|
err = multierror.Append(err, fmt.Errorf("add Device failed: %s", response.Message))
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return errors.NewCommonEdgeXWrapper(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func loadDevicesFromFile(path, serviceName string, lc logger.LoggingClient) ([]requests.AddDeviceRequest, errors.EdgeX) {
|
|
absPath, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, "failed to create absolute path for Devices", err)
|
|
}
|
|
|
|
files, err := os.ReadDir(absPath)
|
|
if err != nil {
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, "failed to read directory for Devices", err)
|
|
}
|
|
|
|
if len(files) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
lc.Infof("Loading pre-defined Devices from %s(%d files found)", absPath, len(files))
|
|
var addDevicesReq, processedDevicesReq []requests.AddDeviceRequest
|
|
for _, file := range files {
|
|
fullPath := filepath.Join(absPath, file.Name())
|
|
processedDevicesReq = processDevices(fullPath, fullPath, serviceName, nil, lc)
|
|
if len(processedDevicesReq) > 0 {
|
|
addDevicesReq = append(addDevicesReq, processedDevicesReq...)
|
|
}
|
|
}
|
|
return addDevicesReq, nil
|
|
}
|
|
|
|
func loadDevicesFromURI(inputURI string, parsedURI *url.URL, serviceName string, secretProvider interfaces.SecretProvider, lc logger.LoggingClient) ([]requests.AddDeviceRequest, errors.EdgeX) {
|
|
// the input URI contains the index file containing the Device list to be loaded
|
|
bytes, err := file.Load(inputURI, secretProvider, lc)
|
|
if err != nil {
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, fmt.Sprintf("failed to load Devices list from URI %s", parsedURI.Redacted()), err)
|
|
}
|
|
|
|
var files []string
|
|
err = json.Unmarshal(bytes, &files)
|
|
if err != nil {
|
|
return nil, errors.NewCommonEdgeX(errors.KindServerError, "could not unmarshal Devices list contents", err)
|
|
}
|
|
|
|
if len(files) == 0 {
|
|
lc.Infof("Index file %s for Devices list is empty", parsedURI.Redacted())
|
|
return nil, nil
|
|
}
|
|
|
|
lc.Infof("Loading pre-defined devices from %s(%d files found)", parsedURI.Redacted(), len(files))
|
|
var addDevicesReq, processedDevicesReq []requests.AddDeviceRequest
|
|
for _, file := range files {
|
|
fullPath, redactedPath := GetFullAndRedactedURI(parsedURI, file, "Device", lc)
|
|
processedDevicesReq = processDevices(fullPath, redactedPath, serviceName, secretProvider, lc)
|
|
if len(processedDevicesReq) > 0 {
|
|
addDevicesReq = append(addDevicesReq, processedDevicesReq...)
|
|
}
|
|
}
|
|
return addDevicesReq, nil
|
|
}
|
|
|
|
func processDevices(fullPath, displayPath, serviceName string, secretProvider interfaces.SecretProvider, lc logger.LoggingClient) []requests.AddDeviceRequest {
|
|
var devices []dtos.Device
|
|
var addDevicesReq []requests.AddDeviceRequest
|
|
|
|
fileType := GetFileType(fullPath)
|
|
|
|
// if the file type is not yaml or json, it cannot be parsed - just return to not break the loop for other devices
|
|
if fileType == OTHER {
|
|
return nil
|
|
}
|
|
|
|
content, err := file.Load(fullPath, secretProvider, lc)
|
|
if err != nil {
|
|
lc.Errorf("Failed to read Devices from %s: %v", displayPath, err)
|
|
return nil
|
|
}
|
|
|
|
switch fileType {
|
|
case YAML:
|
|
d := struct {
|
|
DeviceList []dtos.Device `yaml:"deviceList"`
|
|
}{}
|
|
err = yaml.Unmarshal(content, &d)
|
|
if err != nil {
|
|
lc.Errorf("Failed to YAML decode Devices from %s: %v", displayPath, err)
|
|
return nil
|
|
}
|
|
devices = d.DeviceList
|
|
case JSON:
|
|
err = json.Unmarshal(content, &devices)
|
|
if err != nil {
|
|
lc.Errorf("Failed to JSON decode Devices from %s: %v", displayPath, err)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
for _, device := range devices {
|
|
if _, ok := cache.Devices().ForName(device.Name); ok {
|
|
lc.Infof("Device %s exists, using the existing one", device.Name)
|
|
} else {
|
|
lc.Infof("Device %s not found in Metadata, adding it ...", device.Name)
|
|
device.ServiceName = serviceName
|
|
device.AdminState = models.Unlocked
|
|
device.OperatingState = models.Up
|
|
req := requests.NewAddDeviceRequest(device)
|
|
addDevicesReq = append(addDevicesReq, req)
|
|
}
|
|
}
|
|
return addDevicesReq
|
|
}
|