EdgexAgent/device-gps-go/internal/provision/profiles.go
2025-07-10 20:30:06 +08:00

199 lines
6.7 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"
"github.com/edgexfoundry/device-sdk-go/v4/internal/cache"
bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/container"
"github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/file"
bootstrapInterfaces "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/interfaces"
"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/google/uuid"
"gopkg.in/yaml.v3"
"net/url"
"os"
"path/filepath"
)
const (
jsonExt = ".json"
yamlExt = ".yaml"
ymlExt = ".yml"
)
func LoadProfiles(path string, dic *di.Container) errors.EdgeX {
var addProfilesReq []requests.DeviceProfileRequest
var edgexErr errors.EdgeX
if path == "" {
return nil
}
lc := bootstrapContainer.LoggingClientFrom(dic.Get)
dpc := bootstrapContainer.DeviceProfileClientFrom(dic.Get)
parsedUrl, err := url.Parse(path)
if err != nil {
return errors.NewCommonEdgeX(errors.KindServerError, "failed to parse Device Profile path as a URI", err)
}
if parsedUrl.Scheme == "http" || parsedUrl.Scheme == "https" {
secretProvider := bootstrapContainer.SecretProviderFrom(dic.Get)
addProfilesReq, edgexErr = loadProfilesFromURI(path, parsedUrl, dpc, secretProvider, lc)
if edgexErr != nil {
return edgexErr
}
} else {
addProfilesReq, edgexErr = loadProfilesFromFile(path, dpc, lc)
if edgexErr != nil {
return edgexErr
}
}
if len(addProfilesReq) == 0 {
return nil
}
ctx := context.WithValue(context.Background(), common.CorrelationHeader, uuid.NewString()) // nolint:staticcheck
_, edgexErr = dpc.Add(ctx, addProfilesReq)
return edgexErr
}
func loadProfilesFromFile(path string, dpc interfaces.DeviceProfileClient, lc logger.LoggingClient) ([]requests.DeviceProfileRequest, errors.EdgeX) {
var edgexErr errors.EdgeX
absPath, err := filepath.Abs(path)
if err != nil {
return nil, errors.NewCommonEdgeX(errors.KindServerError, "failed to create absolute path for profiles", err)
}
files, err := os.ReadDir(absPath)
if err != nil {
return nil, errors.NewCommonEdgeX(errors.KindServerError, "failed to read directory for profiles", err)
}
if len(files) == 0 {
return nil, nil
}
lc.Infof("Loading pre-defined Device Profiles from %s(%d files found)", absPath, len(files))
var addProfilesReq, processedProfilesReq []requests.DeviceProfileRequest
for _, file := range files {
fullPath := filepath.Join(absPath, file.Name())
processedProfilesReq, edgexErr = processProfiles(fullPath, fullPath, nil, lc, dpc)
if edgexErr != nil {
return nil, edgexErr
}
if len(processedProfilesReq) > 0 {
addProfilesReq = append(addProfilesReq, processedProfilesReq...)
}
}
return addProfilesReq, nil
}
func loadProfilesFromURI(inputURI string, parsedURI *url.URL, dpc interfaces.DeviceProfileClient, secretProvider bootstrapInterfaces.SecretProvider, lc logger.LoggingClient) ([]requests.DeviceProfileRequest, errors.EdgeX) {
// the input URI contains the index file containing the Profile 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 Device Profile list from URI %s", parsedURI.Redacted()), err)
}
var files map[string]string
err = json.Unmarshal(bytes, &files)
if err != nil {
return nil, errors.NewCommonEdgeX(errors.KindServerError, "could not unmarshal Profile list contents", err)
}
if len(files) == 0 {
lc.Infof("Index file %s for Device Profiles list is empty", parsedURI.Redacted())
return nil, nil
}
lc.Infof("Loading pre-defined Device Profiles from %s(%d files found)", parsedURI.Redacted(), len(files))
var addProfilesReq, processedProfilesReq []requests.DeviceProfileRequest
for name, file := range files {
done, edgexErr := checkDeviceProfile(name, dpc, lc)
if done {
if edgexErr != nil {
return addProfilesReq, edgexErr
}
} else {
fullPath, redactedPath := GetFullAndRedactedURI(parsedURI, file, "Device Profile", lc)
processedProfilesReq, edgexErr = processProfiles(fullPath, redactedPath, secretProvider, lc, dpc)
if edgexErr != nil {
return nil, edgexErr
}
if len(processedProfilesReq) > 0 {
addProfilesReq = append(addProfilesReq, processedProfilesReq...)
}
}
}
return addProfilesReq, nil
}
func processProfiles(fullPath, displayPath string, secretProvider bootstrapInterfaces.SecretProvider, lc logger.LoggingClient, dpc interfaces.DeviceProfileClient) ([]requests.DeviceProfileRequest, errors.EdgeX) {
var profile dtos.DeviceProfile
var addProfilesReq []requests.DeviceProfileRequest
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, nil
}
content, err := file.Load(fullPath, secretProvider, lc)
if err != nil {
lc.Errorf("Failed to read Device Profile from %s: %v", displayPath, err)
return nil, nil
}
switch fileType {
case YAML:
err = yaml.Unmarshal(content, &profile)
if err != nil {
lc.Errorf("Failed to YAML decode Device Profile from %s: %v", displayPath, err)
return nil, nil
}
case JSON:
err = json.Unmarshal(content, &profile)
if err != nil {
lc.Errorf("Failed to JSON decode Device Profile from %s: %v", displayPath, err)
return nil, nil
}
}
done, edgexErr := checkDeviceProfile(profile.Name, dpc, lc)
if done {
if edgexErr != nil {
return addProfilesReq, edgexErr
}
} else {
lc.Infof("Device Profile %s not found in Metadata, adding it ...", profile.Name)
req := requests.NewDeviceProfileRequest(profile)
addProfilesReq = append(addProfilesReq, req)
}
return addProfilesReq, nil
}
func checkDeviceProfile(name string, dpc interfaces.DeviceProfileClient, lc logger.LoggingClient) (bool, errors.EdgeX) {
res, err := dpc.DeviceProfileByName(context.Background(), name)
if err == nil {
lc.Infof("Device Profile %s exists, using the existing one", name)
err = cache.Profiles().CheckAndAdd(dtos.ToDeviceProfileModel(res.Profile))
if err != nil {
return true, errors.NewCommonEdgeX(errors.KindServerError, fmt.Sprintf("failed to cache the profile %s", res.Profile.Name), err)
}
return true, nil
}
return false, nil
}