diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b4d4137 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,32 @@ +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + +name: Create Release +jobs: + build: + permissions: write-all + name: Create Release + runs-on: ubuntu-latest + steps: + - uses: "marvinpinto/action-automatic-releases@latest" + with: + repo_token: "${{ secrets.GITHUB_TOKEN }}" + prerelease: false + + #- uses: actions/checkout@v3 + #- name: Create a Release + # uses: elgohr/Github-Release-Action@v5 + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # with: + # title: ${{github.ref_name}} + #- name: Latest tag + # # You may pin to the exact commit or the version. + # uses: EndBug/latest-tag@v1.6.1 + # with: + # # Name of the tag or branch to update + # ref: ${{ github.ref }} + # # Tag name + # #tag-name: # optional diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4f3f07d --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module github.com/fbuedding/fiware-iot-agent-sdk + +go 1.21.1 + +require ( + github.com/niemeyer/golang v0.0.0-20110826170342-f8c0f811cb19 + github.com/rs/zerolog v1.32.0 +) + +require ( + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + golang.org/x/sys v0.12.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2070c6f --- /dev/null +++ b/go.sum @@ -0,0 +1,17 @@ +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/niemeyer/golang v0.0.0-20110826170342-f8c0f811cb19 h1:HDsKR+rtTZD+ey3J3U+UWJLko6XJkNWaRp0+yn1u0FQ= +github.com/niemeyer/golang v0.0.0-20110826170342-f8c0f811cb19/go.mod h1:vOsSoNMQygvjuK93uIN1lif16OAgfRDlVu7hHS96lPg= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= +github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/iota-config-group.go b/iota-config-group.go new file mode 100644 index 0000000..d90a519 --- /dev/null +++ b/iota-config-group.go @@ -0,0 +1,300 @@ +package iotagentsdk + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strings" + + "github.com/niemeyer/golang/src/pkg/container/vector" + "github.com/rs/zerolog/log" +) + +const ( + urlService = urlBase + "/iot/services" +) + +func (e *MissingFields) Error() string { + return fmt.Sprintf("Error %s: %s", e.Message, e.Fields) +} + +func (sg ConfigGroup) Validate() error { + mF := &MissingFields{make(vector.StringVector, 0), "Missing fields"} + if sg.Apikey == "" { + mF.Fields.Push("Apikey") + } + if sg.Resource == "" { + mF.Fields.Push("Resource") + } + + if mF.Fields.Len() == 0 { + return nil + } else { + return mF + } +} + +type RespReadConfigGroup struct { + Count int `json:"count"` + Services []ConfigGroup `json:"services"` +} + +type ReqCreateConfigGroup struct { + Services []ConfigGroup `json:"services"` +} + +func (i IoTA) ReadConfigGroup(fs FiwareService, r Resource, a Apikey) (*RespReadConfigGroup, error) { + url := urlService + fmt.Sprintf("?resource=%s&apikey=%s", r, a) + + method := "GET" + + client := i.Client() + req, err := http.NewRequest(method, fmt.Sprintf(url, i.Host, i.Port), nil) + if err != nil { + return nil, fmt.Errorf("Error while getting service: %w", err) + } + req.Header.Add("fiware-service", fs.Service) + req.Header.Add("fiware-servicepath", fs.ServicePath) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + res, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("Error while getting service: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + resData, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("Error while eding response body %w", err) + } + var apiError ApiError + json.Unmarshal(resData, &apiError) + return nil, apiError + } + + responseData, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("Error while getting service: %w", err) + } + + var respReadConfigGroup RespReadConfigGroup + json.Unmarshal(responseData, &respReadConfigGroup) + return &respReadConfigGroup, nil +} + +func (i IoTA) ListConfigGroups(fs FiwareService) (*RespReadConfigGroup, error) { + url := urlService + + method := "GET" + + client := i.Client() + req, err := http.NewRequest(method, fmt.Sprintf(url, i.Host, i.Port), nil) + if err != nil { + return nil, fmt.Errorf("Error while getting service: %w", err) + } + req.Header.Add("fiware-service", fs.Service) + req.Header.Add("fiware-servicepath", fs.ServicePath) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + res, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("Error while getting service: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + resData, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("Error while eding response body %w", err) + } + var apiError ApiError + json.Unmarshal(resData, &apiError) + return nil, apiError + } + + responseData, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("Error while getting service: %w", err) + } + + var respReadConfigGroup RespReadConfigGroup + json.Unmarshal(responseData, &respReadConfigGroup) + return &respReadConfigGroup, nil +} + +func (i IoTA) ConfigGroupExists(fs FiwareService, r Resource, a Apikey) bool { + tmp, err := i.ReadConfigGroup(fs, r, a) + if err != nil { + return false + } + return tmp.Count > 0 +} + +func (i IoTA) CreateConfigGroup(fs FiwareService, sg ConfigGroup) error { + sgs := [1]ConfigGroup{sg} + return i.CreateConfigGroups(fs, sgs[:]) +} + +func (i IoTA) CreateConfigGroups(fs FiwareService, sgs []ConfigGroup) error { + for _, sg := range sgs { + err := sg.Validate() + if err != nil { + return err + } + } + reqCreateConfigGroup := ReqCreateConfigGroup{} + reqCreateConfigGroup.Services = sgs[:] + method := "POST" + + payload, err := json.Marshal(reqCreateConfigGroup) + if err != nil { + log.Panic().Err(err).Msg("Could not Marshal struct") + } + client := i.Client() + req, err := http.NewRequest(method, fmt.Sprintf(urlService, i.Host, i.Port), bytes.NewBuffer(payload)) + if err != nil { + return fmt.Errorf("Error while creating Request %w", err) + } + req.Header.Add("fiware-service", fs.Service) + req.Header.Add("fiware-servicepath", fs.ServicePath) + req.Header.Add("Content-Type", "application/json") + + res, err := client.Do(req) + if err != nil { + return fmt.Errorf("Error while requesting resource %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusCreated { + resData, err := io.ReadAll(res.Body) + if err != nil { + return fmt.Errorf("Error while eding response body %w", err) + } + var apiError ApiError + json.Unmarshal(resData, &apiError) + return apiError + } + + return nil +} + +func (i IoTA) UpdateConfigGroup(fs FiwareService, r Resource, a Apikey, sg ConfigGroup) error { + err := sg.Validate() + if err != nil { + return err + } + url := urlService + fmt.Sprintf("?resource=%s&apikey=%s", r, a) + method := "PUT" + + payload, err := json.Marshal(sg) + if err != nil { + log.Panic().Err(err).Msg("Could not Marshal struct") + } + if string(payload) == "{}" { + return nil + } + client := i.Client() + req, err := http.NewRequest(method, fmt.Sprintf(url, i.Host, i.Port), bytes.NewBuffer(payload)) + if err != nil { + return fmt.Errorf("Error while creating Request %w", err) + } + req.Header.Add("fiware-service", fs.Service) + req.Header.Add("fiware-servicepath", fs.ServicePath) + req.Header.Add("Content-Type", "application/json") + + res, err := client.Do(req) + if err != nil { + return fmt.Errorf("Error while requesting resource %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusNoContent { + resData, err := io.ReadAll(res.Body) + if err != nil { + return fmt.Errorf("Error while eding response body %w", err) + } + var apiError ApiError + json.Unmarshal(resData, &apiError) + return apiError + } + + return nil +} + +func (i IoTA) DeleteConfigGroup(fs FiwareService, r Resource, a Apikey) error { + url := urlService + fmt.Sprintf("?resource=%s&apikey=%s", r, a) + + method := http.MethodDelete + + client := http.Client{} + req, err := http.NewRequest(method, fmt.Sprintf(url, i.Host, i.Port), strings.NewReader("")) + if err != nil { + return fmt.Errorf("Error while creating Request %w", err) + } + + req.Header.Add("fiware-service", fs.Service) + req.Header.Add("fiware-servicepath", fs.ServicePath) + + res, err := client.Do(req) + if err != nil { + return fmt.Errorf("Error while requesting resource %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusNoContent { + resData, err := io.ReadAll(res.Body) + if err != nil { + return fmt.Errorf("Error while eding response body %w", err) + } + var apiError ApiError + json.Unmarshal(resData, &apiError) + return apiError + } + + return nil +} + +func (i IoTA) UpsertConfigGroup(fs FiwareService, sg ConfigGroup) error { + exists := i.ConfigGroupExists(fs, sg.Resource, sg.Apikey) + if !exists { + log.Debug().Msg("Creating service group...") + err := i.CreateConfigGroup(fs, sg) + if err != nil { + return err + } + } else { + log.Debug().Msg("Update service group...") + err := i.UpdateConfigGroup(fs, sg.Resource, sg.Apikey, sg) + if err != nil { + return err + } + } + return nil +} + +func (i IoTA) CreateConfigGroupWSE(fs FiwareService, sg *ConfigGroup) error { + if sg == nil { + return errors.New("Service group reference cannot be nil") + } + + err := i.CreateConfigGroup(fs, *sg) + if err != nil { + return err + } + + sgTmp, err := i.ReadConfigGroup(fs, sg.Resource, sg.Apikey) + if err != nil { + return err + } + + if sgTmp.Count == 0 { + return errors.New("No service group created") + } + *sg = *&sgTmp.Services[0] + + return nil +} diff --git a/iota-device.go b/iota-device.go new file mode 100644 index 0000000..2c21d2f --- /dev/null +++ b/iota-device.go @@ -0,0 +1,312 @@ +package iotagentsdk + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + u "net/url" + + "github.com/niemeyer/golang/src/pkg/container/vector" + log "github.com/rs/zerolog/log" +) + +const ( + urlDevice = urlBase + "/iot/devices" +) + +type reqCreateDevice struct { + Devices []Device `json:"devices"` +} + +type respListDevices struct { + Count int `json:"count"` + Devices []Device `json:"devices"` +} + +func (d Device) Validate() error { + mF := &MissingFields{make(vector.StringVector, 0), "Missing fields"} + if d.Id == "" { + mF.Fields.Push("Id") + } + + if mF.Fields.Len() == 0 { + return nil + } else { + return mF + } +} + +func (i IoTA) ReadDevice(fs FiwareService, id DeciveId) (*Device, error) { + url, err := u.JoinPath(fmt.Sprintf(urlDevice, i.Host, i.Port), u.PathEscape(string(id))) + if err != nil { + return nil, err + } + method := "GET" + + client := i.Client() + req, err := http.NewRequest(method, url, nil) + if err != nil { + return nil, fmt.Errorf("Error while getting service: %w", err) + } + req.Header.Add("fiware-service", fs.Service) + req.Header.Add("fiware-servicepath", fs.ServicePath) + + res, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("Error while getting service: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + resData, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("Error while eding response body %w", err) + } + var apiError ApiError + err = json.Unmarshal(resData, &apiError) + if err != nil { + log.Panic().Err(err).Msg("Could not Marshal struct") + } + return nil, apiError + } + + responseData, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("Error while getting service: %w", err) + } + + var device Device + json.Unmarshal(responseData, &device) + if err != nil { + log.Panic().Err(err).Msg("Could not Marshal struct") + } + return &device, nil +} + +func (i IoTA) DeviceExists(fs FiwareService, id DeciveId) bool { + _, err := i.ReadDevice(fs, id) + if err != nil { + return false + } + return true +} + +func (i IoTA) ListDevices(fs FiwareService) (*respListDevices, error) { + url := fmt.Sprintf(urlDevice, i.Host, i.Port) + + method := "GET" + + client := i.Client() + req, err := http.NewRequest(method, url, nil) + if err != nil { + return nil, fmt.Errorf("Error while getting service: %w", err) + } + req.Header.Add("fiware-service", fs.Service) + req.Header.Add("fiware-servicepath", fs.ServicePath) + + res, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("Error while getting service: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + resData, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("Error while eding response body %w", err) + } + var apiError ApiError + json.Unmarshal(resData, &apiError) + if err != nil { + log.Panic().Err(err).Msg("Could not Marshal struct") + } + return nil, apiError + } + + responseData, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("Error while getting service: %w", err) + } + + var respDevices respListDevices + json.Unmarshal(responseData, &respDevices) + return &respDevices, nil +} + +func (i IoTA) CreateDevices(fs FiwareService, ds []Device) error { + for _, sg := range ds { + err := sg.Validate() + if err != nil { + return err + } + } + rcd := reqCreateDevice{} + rcd.Devices = ds[:] + method := "POST" + + payload, err := json.Marshal(rcd) + if err != nil { + log.Panic().Err(err).Msg("Could not Marshal struct") + } + client := i.Client() + req, err := http.NewRequest(method, fmt.Sprintf(urlDevice, i.Host, i.Port), bytes.NewBuffer(payload)) + if err != nil { + return fmt.Errorf("Error while creating Request %w", err) + } + req.Header.Add("fiware-service", fs.Service) + req.Header.Add("fiware-servicepath", fs.ServicePath) + req.Header.Add("Content-Type", "application/json") + + res, err := client.Do(req) + if err != nil { + return fmt.Errorf("Error while requesting resource %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusCreated { + resData, err := io.ReadAll(res.Body) + if err != nil { + return fmt.Errorf("Error while eding response body %w", err) + } + var apiError ApiError + json.Unmarshal(resData, &apiError) + return apiError + } + + return nil +} + +func (i IoTA) CreateDevice(fs FiwareService, d Device) error { + ds := [1]Device{d} + return i.CreateDevices(fs, ds[:]) +} + +func (i IoTA) UpdateDevice(fs FiwareService, d Device) error { + err := d.Validate() + if err != nil { + return err + } + + url, err := u.JoinPath(fmt.Sprintf(urlDevice, i.Host, i.Port), u.PathEscape(string(d.Id))) + + // Ensure these fields are not set + d.Id = "" + d.Transport = "" + + method := "PUT" + + payload, err := json.Marshal(d) + if err != nil { + log.Panic().Err(err).Msg("Could not Marshal struct") + } + if string(payload) == "{}" { + return nil + } + client := i.Client() + req, err := http.NewRequest(method, url, bytes.NewBuffer(payload)) + if err != nil { + return fmt.Errorf("Error while creating Request %w", err) + } + req.Header.Add("fiware-service", fs.Service) + req.Header.Add("fiware-servicepath", fs.ServicePath) + req.Header.Add("Content-Type", "application/json") + + res, err := client.Do(req) + if err != nil { + return fmt.Errorf("Error while requesting resource %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusNoContent { + resData, err := io.ReadAll(res.Body) + if err != nil { + return fmt.Errorf("Error while eding response body %w", err) + } + var apiError ApiError + json.Unmarshal(resData, &apiError) + return apiError + } + return nil +} + +func (i IoTA) DeleteDevice(fs FiwareService, id DeciveId) error { + url, err := u.JoinPath(fmt.Sprintf(urlDevice, i.Host, i.Port), u.PathEscape(string(id))) + if err != nil { + return err + } + method := "DELETE" + + client := i.Client() + req, err := http.NewRequest(method, url, nil) + if err != nil { + return fmt.Errorf("Error while getting service: %w", err) + } + req.Header.Add("fiware-service", fs.Service) + req.Header.Add("fiware-servicepath", fs.ServicePath) + + res, err := client.Do(req) + if err != nil { + return fmt.Errorf("Error while getting service: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusNoContent { + resData, err := io.ReadAll(res.Body) + if err != nil { + return fmt.Errorf("Error while eding response body %w", err) + } + var apiError ApiError + json.Unmarshal(resData, &apiError) + return apiError + } + return nil +} + +func (i IoTA) UpsertDevice(fs FiwareService, d Device) error { + exists := i.DeviceExists(fs, d.Id) + if !exists { + log.Debug().Msg("Creating device...") + err := i.CreateDevice(fs, d) + if err != nil { + return err + } + } else { + log.Debug().Msg("Update device...") + dTmp, err := i.ReadDevice(fs, d.Id) + if err != nil { + return err + } + + if dTmp.EntityName == "" { + return errors.New("Error before getting updating device: No entity_name") + } + + d.Transport = "" + d.EntityName = dTmp.EntityName + err = i.UpdateDevice(fs, d) + if err != nil { + return err + } + } + return nil +} + +// Creates a device an updates the +func (i IoTA) CreateDeviceWSE(fs FiwareService, d *Device) error { + if d == nil { + return errors.New("Device reference cannot be nil") + } + err := i.CreateDevice(fs, *d) + if err != nil { + return err + } + dTmp, err := i.ReadDevice(fs, d.Id) + if err != nil { + return err + } + *d = *dTmp + return nil +} diff --git a/iota-device_test.go b/iota-device_test.go new file mode 100644 index 0000000..b228de3 --- /dev/null +++ b/iota-device_test.go @@ -0,0 +1,81 @@ +package iotagentsdk_test + +import ( + "testing" + + i "github.com/fbuedding/fiware-iot-agent-sdk" +) + +func TestReadDevice(t *testing.T) { + respD, err := iota.ReadDevice(fs, deviceId) + if err != nil { + t.Error(err) + } + if respD.EntityName != entityName { + t.Fail() + } +} + +func TestListDevice(t *testing.T) { + respD, err := iota.ListDevices(fs) + if err != nil { + t.Error(err) + } + if respD.Count != 1 { + t.Fail() + } + + if respD.Devices[0].EntityName != entityName { + t.Fail() + } +} + +func TestUpdateDevice(t *testing.T) { + dtmp := d + dtmp.EntityName = updatedEntityName + dtmp.EntityType = "test" + dtmp.Transport = "MQTT" + err := iota.UpdateDevice(fs, dtmp) + if err != nil { + t.Error(err) + } + dUpdated, _ := iota.ReadDevice(fs, d.Id) + if dUpdated.EntityName != updatedEntityName { + t.Fail() + } + dtmp1 := i.Device{Id: deviceId} + + err = iota.UpdateDevice(fs, dtmp1) + if err != nil { + t.Log("Device shouldn't updatet empty body") + t.Error(err) + } +} + +func TestDeleteDevice(t *testing.T) { + err := iota.DeleteDevice(fs, d.Id) + if err != nil { + t.Error(err) + } +} + +func TestUpsertDevice(t *testing.T) { + err := iota.UpsertDevice(fs, d) + if err != nil { + t.Error(err) + } + err = iota.UpsertDevice(fs, d) + if err != nil { + t.Error(err) + } + iota.DeleteDevice(fs, d.Id) +} + +func TestCreateDeviceWSE(t *testing.T) { + dtemp := d + err := iota.CreateDeviceWSE(fs, &dtemp) + if err != nil { + t.Error(err) + } + t.Log(dtemp) +} diff --git a/iota-sdk.go b/iota-sdk.go new file mode 100644 index 0000000..4c41ad1 --- /dev/null +++ b/iota-sdk.go @@ -0,0 +1,102 @@ +package iotagentsdk + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "slices" + "strings" + "time" + + "github.com/rs/zerolog" + log "github.com/rs/zerolog/log" +) + +const ( + urlBase = "http://%v:%d" + urlHealthcheck = urlBase + "/iot/about" +) + +func (e ApiError) Error() string { + return fmt.Sprintf("%s: %s", e.Name, e.Message) +} + +func init() { + logLvl := os.Getenv("LOG_LEVEL") + if logLvl == "" { + logLvl = "panic" + } + SetLogLevel(logLvl) +} + +func SetLogLevel(ll string) { + ll = strings.ToLower(ll) + switch ll { + case "trace": + zerolog.SetGlobalLevel(zerolog.TraceLevel) + case "debug": + zerolog.SetGlobalLevel(zerolog.DebugLevel) + case "info": + zerolog.SetGlobalLevel(zerolog.InfoLevel) + case "warning": + zerolog.SetGlobalLevel(zerolog.WarnLevel) + case "error": + zerolog.SetGlobalLevel(zerolog.ErrorLevel) + case "fatal": + zerolog.SetGlobalLevel(zerolog.FatalLevel) + case "panic": + zerolog.SetGlobalLevel(zerolog.PanicLevel) + default: + log.Fatal().Msg("Log level need to be one of this: [TRACE DEBUG INFO WARNING ERROR FATAL PANIC]") + } +} + +func (i IoTA) Healthcheck() (*RespHealthcheck, error) { + response, err := http.Get(fmt.Sprintf(urlHealthcheck, i.Host, i.Port)) + if err != nil { + return nil, fmt.Errorf("Error while Healthcheck: %w", err) + } + defer response.Body.Close() + + responseData, err := io.ReadAll(response.Body) + if err != nil { + return nil, fmt.Errorf("Error while Healthcheck: %w", err) + } + var respHealth RespHealthcheck + json.Unmarshal(responseData, &respHealth) + if respHealth.Version == "" { + return nil, fmt.Errorf("Error healtchecking IoT-Agent, host: %s", i.Host) + } + log.Debug().Str("Response healthcheck", string(responseData)).Any("Healthcheck", respHealth).Send() + return &respHealth, nil +} + +func (i IoTA) GetAllServicePathsForService(service string) ([]string, error) { + cgs, err := i.ListConfigGroups(FiwareService{service, "/*"}) + if err != nil { + return nil, err + } + + if cgs.Count == 0 { + return nil, nil + } + + servicePaths := []string{} + for _, cg := range cgs.Services { + if !slices.Contains(servicePaths, cg.ServicePath) { + servicePaths = append(servicePaths, cg.ServicePath) + } + } + + return servicePaths, nil +} + +func (i IoTA) Client() *http.Client { + if i.client == nil { + log.Debug().Msg("Creating http client") + i.client = &http.Client{Timeout: 1 * time.Second} + } + return i.client +} diff --git a/iota-service-group_test.go b/iota-service-group_test.go new file mode 100644 index 0000000..3ef0af5 --- /dev/null +++ b/iota-service-group_test.go @@ -0,0 +1,74 @@ +package iotagentsdk_test + +import ( + "testing" +) + +func TestReadConfigGroup(t *testing.T) { + t.Log("Testing ReadConfigGroup") + respD, err := iota.ReadConfigGroup(fs, resource, apiKey) + if err != nil { + t.Error(err) + } + if respD.Count == 0 { + t.Fail() + } +} + +func TestListConfigGroup(t *testing.T) { + t.Log("Testing ListConfigGroup") + + respD, err := iota.ReadConfigGroup(fs, resource, apiKey) + if err != nil { + t.Error(err) + } + if respD.Count == 0 { + t.Fail() + } +} + +func TestUpdateConfigGroup(t *testing.T) { + t.Log("Testing UpdateConfigGroup") + sgtmp := sg + sgtmp.Autoprovision = true + err := iota.UpdateConfigGroup(fs, resource, apiKey, sgtmp) + if err != nil { + t.Error(err) + } + sgUpdated, _ := iota.ReadConfigGroup(fs, resource, apiKey) + if sgUpdated.Services[0].Autoprovision != true { + t.Fail() + } +} + +func TestDeleteConfigGroup(t *testing.T) { + t.Log("Testing deleteConfigGroup") + err := iota.DeleteConfigGroup(fs, resource, apiKey) + if err != nil { + t.Error(err) + } +} + +func TestUpsertConfigGroup(t *testing.T) { + t.Log("Testing UpsertConfigGroup") + + err := iota.UpsertConfigGroup(fs, sg) + if err != nil { + t.Error(err) + } + t.Log("Testing UpsertConfigGroup again") + err = iota.UpsertConfigGroup(fs, sg) + if err != nil { + t.Error(err) + } + iota.DeleteConfigGroup(fs, resource, apiKey) +} + +func TestCreatConfigGroupWSE(t *testing.T) { + sgtemp := sg + err := iota.CreateConfigGroupWSE(fs, &sgtemp) + if err != nil { + t.Error(err) + } + t.Log(sgtemp) +} diff --git a/iota-types.go b/iota-types.go new file mode 100644 index 0000000..4bd2fd5 --- /dev/null +++ b/iota-types.go @@ -0,0 +1,130 @@ +package iotagentsdk + +import ( + "net/http" + + "github.com/niemeyer/golang/src/pkg/container/vector" +) + +type IoTA struct { + Host string + Port int + client *http.Client +} + +type FiwareService struct { + Service string + ServicePath string +} + +type RespHealthcheck struct { + LibVersion string `json:"libVersion"` + Port string `json:"port"` + BaseRoot string `json:"baseRoot"` + Version string `json:"version"` +} +type ApiError struct { + Name string `json:"name"` + Message string `json:"message"` +} + +// these are nearly the same, but for typesafety differnt structs +type Attribute struct { + ObjectID string `json:"object_id,omitempty" formam:"object_id"` + Name string `json:"name" formam:"name"` + Type string `json:"type" formam:"type"` + Expression string `json:"expression,omitempty" formam:"expression"` + SkipValue string `json:"skipValue,omitempty" formam:"skipValue"` + EntityName string `json:"entity_name,omitempty" formam:"entity_name"` + EntityType string `json:"entity_type,omitempty" formam:"entity_type"` + Metadata map[string]Metadata `json:"metadata,omitempty" formam:"metadata"` +} + +type LazyAttribute struct { + ObjectID string `json:"object_id,omitempty" formam:"object_id"` + Name string `json:"name" formam:"name"` + Type string `json:"type" formam:"type"` + Metadata map[string]Metadata `json:"metadata,omitempty" formam:"metadata"` +} + +type StaticAttribute struct { + ObjectID string `json:"object_id,omitempty" formam:"object_id"` + Name string `json:"name" formam:"name"` + Type string `json:"type" formam:"type"` + Metadata map[string]Metadata `json:"metadata,omitempty" formam:"metadata"` +} + +type Command struct { + ObjectID string `json:"object_id,omitempty" formam:"object_id"` + Name string `json:"name" formam:"name"` + Type string `json:"type" formam:"type"` + Expression string `json:"expression,omitempty" formam:"expression"` + PayloadType string `json:"payloadType,omitempty" formam:"payloadType"` + ContentType string `json:"contentType,omitempty" formam:"contentType"` + Metadata map[string]Metadata `json:"metadata,omitempty" formam:"metadata"` +} + +// Extra type for x-form-data +type Metadata struct { + Type string `json:"type" formam:"type"` + Value string `json:"value" formam:"value"` +} + +type ( + Apikey string + Resource string +) + +// see https://iotagent-node-lib.readthedocs.io/en/latest/api.html#service-group-datamodel +type ConfigGroup struct { + Service string `json:"service,omitempty" formam:"service"` + ServicePath string `json:"subservice,omitempty" formam:"subservice"` + Resource Resource `json:"resource" formam:"resource"` + Apikey Apikey `json:"apikey" formam:"apikey"` + Timestamp *bool `json:"timestamp,omitempty" formam:"timestamp"` + EntityType string `json:"entity_type,omitempty" formam:"entity_type"` + Trust string `json:"trust,omitempty" formam:"trust"` + CbHost string `json:"cbHost,omitempty" formam:"cbHost"` + Lazy []LazyAttribute `json:"lazy,omitempty" formam:"lazy"` + Commands []Command `json:"commands,omitempty" formam:"commands"` + Attributes []Attribute `json:"attributes,omitempty" formam:"attributes"` + StaticAttributes []StaticAttribute `json:"static_attributes,omitempty" formam:"static_attributes"` + InternalAttributes []interface{} `json:"internal_attributes,omitempty" formam:"internal_attributes"` + ExplicitAttrs string `json:"explicitAttrs,omitempty" formam:"explicitAttrs"` + EntityNameExp string `json:"entityNameExp,omitempty" formam:"entityNameExp"` + NgsiVersion string `json:"ngsiVersion,omitempty" formam:"ngsiVersion"` + DefaultEntityNameConjunction string `json:"defaultEntityNameConjunction,omitempty" formam:"defaultEntityNameConjunction"` + Autoprovision bool `json:"autoprovision,omitempty" formam:"autoprovision"` + PayloadType string `json:"payloadType,omitempty" formam:"payloadType"` + Transport string `json:"transport,omitempty" formam:"transport"` + Endpoint string `json:"endpoint,omitempty" formam:"endpoint"` +} + +type DeciveId string + +type Device struct { + Id DeciveId `json:"device_id,omitempty" formam:"device_id"` + Service string `json:"service,omitempty" formam:"service"` + ServicePath string `json:"service_path,omitempty" formam:"service_path"` + EntityName string `json:"entity_name,omitempty" formam:"entity_name"` + EntityType string `json:"entity_type,omitempty" formam:"entity_type"` + Timezone string `json:"timezon,omitempty" formam:"timezone"` + Timestamp *bool `json:"timestamp,omitempty" formam:"timestamp"` + Apikey Apikey `json:"apikey,omitempty" formam:"apikey"` + Endpoint string `json:"endpoint,omitempty" formam:"endpoint"` + Protocol string `json:"protocol,omitempty" formam:"protocol"` + Transport string `json:"transport,omitempty" formam:"transport"` + Attributes []Attribute `json:"attributes,omitempty" formam:"attributes"` + Commands []Command `json:"commands,omitempty" formam:"commands"` + Lazy []LazyAttribute `json:"lazy,omitempty" formam:"lazy"` + StaticAttributes []StaticAttribute `json:"static_attributes,omitempty" formam:"static_attributes"` + InternalAttributes []interface{} `json:"internal_attributes,omitempty" formam:"internal_attributes"` + ExplicitAttrs string `json:"explicitAttrs,omitempty" formam:"explicitAttrs"` + NgsiVersion string `json:"ngsiVersion,omitempty" formam:"ngsiVersion"` + PayloadType string `json:"payloadType,omitempty" formam:"payloadType"` +} + +type MissingFields struct { + Fields vector.StringVector + Message string +} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..ecf92c5 --- /dev/null +++ b/main_test.go @@ -0,0 +1,62 @@ +package iotagentsdk_test + +import ( + "testing" + + i "github.com/fbuedding/fiware-iot-agent-sdk" + "github.com/rs/zerolog/log" +) + +var ( + iota i.IoTA + fs i.FiwareService + d i.Device + sg i.ConfigGroup +) + +const ( + deviceId = i.DeciveId("test_device") + entityName = "TestEntityName" + updatedEntityName = "TestEntityNameUpdated" + service = "testing" + servicePath = "/" + resource = i.Resource("/iot/d") + apiKey = "testKey" +) + +func TestMain(m *testing.M) { + iota = i.IoTA{Host: "localhost", Port: 4061} + fs = i.FiwareService{Service: service, ServicePath: servicePath} + d = i.Device{Id: deviceId, EntityName: entityName} + sg = i.ConfigGroup{ + Service: service, + ServicePath: servicePath, + Resource: resource, + Apikey: apiKey, + Autoprovision: false, + } + iota.DeleteDevice(fs, d.Id) + err := iota.CreateDevice(fs, d) + if err != nil { + log.Fatal().Err(err).Msg("Could not create device for tests") + } + iota.DeleteConfigGroup(fs, resource, apiKey) + err = iota.CreateConfigGroup(fs, sg) + if err != nil { + log.Fatal().Err(err).Msg("Could not create service group for tests") + } + m.Run() + teardown() +} + +func teardown() { + err := iota.DeleteDevice(fs, d.Id) + if err != nil { + log.Fatal().Err(err).Msg("Could not create device for teardown") + } + + err = iota.DeleteConfigGroup(fs, resource, apiKey) + if err != nil { + log.Fatal().Err(err).Msg("Could not create device for teardown") + } +}