Skip to content

Commit

Permalink
Add trigger validation
Browse files Browse the repository at this point in the history
Add trigger validation on sync, removing invalid payload from process queue

Signed-off-by: Eddy Babetto <eddy.babetto@secomind.com>
  • Loading branch information
eddbbt committed Nov 20, 2023
1 parent d72bb53 commit 1e359d0
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 86 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ astartectl_*
.gobuild
dist/
/result
.idea/

# Direnv
.direnv/
Expand Down
2 changes: 1 addition & 1 deletion cmd/appengine/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -1241,7 +1241,7 @@ func getProtoInterface(deviceID string, deviceIdentifierType client.DeviceIdenti
// Just a trick to trick the parser into doing the right thing.
if isParametricInterface {
iface.Mappings = []interfaces.AstarteInterfaceMapping{
interfaces.AstarteInterfaceMapping{
{
Endpoint: "/it/%{is}/parametric",
},
}
Expand Down
118 changes: 60 additions & 58 deletions cmd/realm/triggers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ package realm
import (
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/astarte-platform/astarte-go/triggers"
"github.com/astarte-platform/astartectl/utils"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"os"
"path/filepath"
)

// triggersCmd represents the triggers command
Expand Down Expand Up @@ -72,10 +72,10 @@ var triggersDeleteCmd = &cobra.Command{
}

var triggersSaveCmd = &cobra.Command{
Use: "save [destination-path]",
Use: "save <destination-path>",
Short: "Save triggers to a local folder",
Long: `Save each trigger in a realm to a local folder. Each trigger will
be saved in a dedicated file whose name will be in the form '<trigger_name>_v<version>.json'.
be saved in a dedicated file whose name will be in the form '<trigger_name>.json'.
When no destination path is set, triggers will be saved in the current working directory.
This command does not support the --to-curl flag.`,
Example: ` astartectl realm-management triggers save`,
Expand All @@ -84,11 +84,11 @@ This command does not support the --to-curl flag.`,
}

var triggersSyncCmd = &cobra.Command{
Use: "sync <interface_files> [...]",
Use: "sync <trigger_files> [...]",
Short: "Synchronize triggers",
Long: `Synchronize triggers in the realm with the given files.
All given files will be parsed, and only new triggers will be installed in the
realm, depending on the realm's state. In order to force triggers update, use --force flag`,
realm, depending on the realm's state. In order to force triggers update, use --force flag.`,
Example: ` astartectl realm-management triggers sync triggers/*.json`,
Args: cobra.MinimumNArgs(1),
RunE: triggersSyncF,
Expand All @@ -115,12 +115,10 @@ func triggersListF(command *cobra.Command, args []string) error {
}

func triggersShowF(command *cobra.Command, args []string) error {

triggerName := args[0]
triggerDefinition, _ := getTriggerDefinition(realm, triggerName)
respJSON, _ := json.MarshalIndent(triggerDefinition, "", " ")
fmt.Println(string(respJSON))

return nil
}

Expand All @@ -130,7 +128,7 @@ func triggersInstallF(command *cobra.Command, args []string) error {
return err
}

var triggerBody map[string]interface{}
var triggerBody triggers.AstarteTrigger
err = json.Unmarshal(triggerFile, &triggerBody)
if err != nil {
return err
Expand Down Expand Up @@ -197,7 +195,8 @@ func triggersSaveF(command *cobra.Command, args []string) error {
os.Exit(1)
}

respJSON, err := json.MarshalIndent(triggerDefinition, "", " ")
respJSON, err := json.MarshalIndent(*triggerDefinition, "", " ")

if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
Expand Down Expand Up @@ -226,28 +225,33 @@ func triggersSyncF(command *cobra.Command, args []string) error {
os.Exit(1)
}

triggerToInstall := []map[string]interface{}{}
triggerToUpdate := []map[string]interface{}{}
triggersToInstall := []triggers.AstarteTrigger{}
triggersToUpdate := []triggers.AstarteTrigger{}
invalidTriggers := []string{}

for _, f := range args {
triggerFile, err := os.ReadFile(f)
if err != nil {
return err
}
if !validateTrigger(f) {
invalidTriggers = append(invalidTriggers, f)
continue
}

var astarteTrigger map[string]interface{}
var astarteTrigger triggers.AstarteTrigger
if err = json.Unmarshal(triggerFile, &astarteTrigger); err != nil {
return err
}

if _, err := getTriggerDefinition(realm, astarteTrigger["name"].(string)); err != nil {
if _, err := getTriggerDefinition(realm, astarteTrigger.Name); err != nil {
// The trigger does not exist
triggerToInstall = append(triggerToInstall, astarteTrigger)
triggersToInstall = append(triggersToInstall, astarteTrigger)
} else {
triggerToUpdate = append(triggerToUpdate, astarteTrigger)
triggersToUpdate = append(triggersToUpdate, astarteTrigger)
}

if len(triggerToInstall) == 0 && len(triggerToUpdate) == 0 {
if len(triggersToInstall) == 0 && len(triggersToUpdate) == 0 {
// All good in the hood
fmt.Println("Your realm is in sync with the provided triggers files")
return nil
Expand All @@ -257,80 +261,63 @@ func triggersSyncF(command *cobra.Command, args []string) error {
// Notify the user about what we're about to do
list := []string{}

for _, v := range triggerToInstall {
list = append(list, v["name"].(string))
for _, trigger := range triggersToInstall {
list = append(list, trigger.Name)
}

list_existing := []string{}

for _, v := range triggerToUpdate {
list_existing = append(list_existing, v["name"].(string))
for _, trigger := range triggersToUpdate {
list_existing = append(list_existing, trigger.Name)
}

// Start syncing.
fmt.Printf("The following triggers are invalid and thus will not be processed: %+q \n", invalidTriggers)

//install new triggers
if len(triggerToInstall) > 0 {
if len(triggersToInstall) > 0 {

fmt.Printf("\n")
fmt.Printf("The following new triggers will be installed: %+q \n", list)
fmt.Printf("\n")

if ok, err := utils.AskForConfirmation("Do you want to continue?"); !ok || err != nil {
fmt.Printf("aborting")
return nil
}

for _, v := range triggerToInstall {
if err := installTrigger(realm, v); err != nil {
fmt.Fprintf(os.Stderr, "Could not install trigger %s: %s\n", v["name"].(string), err)
for _, trigger := range triggersToInstall {
if err := installTrigger(realm, trigger); err != nil {
fmt.Fprintf(os.Stderr, "Could not install trigger %s: %s\n", trigger.Name, err)
} else {
fmt.Printf("trigger %s installed successfully\n", v["name"].(string))
fmt.Printf("trigger %s installed successfully\n", trigger.Name)
}
}

fmt.Printf("\n")

}

if len(triggerToUpdate) > 0 {

if len(triggersToUpdate) > 0 {
y, err := command.Flags().GetBool("force")
if err != nil {
return err
}

if y {
fmt.Printf("The following triggers already exists and WILL be DELETED and RECREATED: %+q \n", list_existing)
fmt.Printf("\n")
if ok, err := utils.AskForConfirmation("Do you want to continue?"); !ok || err != nil {
fmt.Printf("aborting")
return nil
}

for _, v := range triggerToUpdate {
if err := updateTrigger(realm, v["name"].(string), v); err != nil {
fmt.Fprintf(os.Stderr, "Could not update trigger %s: %s\n", v["name"].(string), err)
for _, trigger := range triggersToUpdate {
if err := updateTrigger(realm, trigger.Name, trigger); err != nil {
fmt.Fprintf(os.Stderr, "Could not update trigger %s: %s\n", trigger.Name, err)
} else {
fmt.Printf("trigger %s updated successfully\n", v["name"].(string))
fmt.Printf("trigger %s updated successfully\n", trigger.Name)
}
}
fmt.Printf("\n")
fmt.Printf("\n")

} else {

// Start syncing.
fmt.Printf("The following triggers already exists and WILL NOT be updated: %+q \n", list_existing)
fmt.Printf("\n")

}
}

return nil
}

func installTrigger(realm string, trigger map[string]interface{}) error {
func installTrigger(realm string, trigger triggers.AstarteTrigger) error {
installTriggerCall, err := astarteAPIClient.InstallTrigger(realm, trigger)
if err != nil {
return err
Expand All @@ -350,8 +337,7 @@ func installTrigger(realm string, trigger map[string]interface{}) error {
return nil
}

func updateTrigger(realm string, triggername string, newtrig map[string]interface{}) error {

func updateTrigger(realm string, triggername string, newtrig triggers.AstarteTrigger) error {
deleteTriggercall, err := astarteAPIClient.DeleteTrigger(realm, triggername)
if err != nil {
return err
Expand Down Expand Up @@ -401,14 +387,14 @@ func listTriggers(realm string) ([]string, error) {
return rawlistTriggers.([]string), nil
}

func getTriggerDefinition(realm, triggerName string) (map[string]interface{}, error) {
func getTriggerDefinition(realm, triggerName string) (*triggers.AstarteTrigger, error) {
getTriggerCall, err := astarteAPIClient.GetTrigger(realm, triggerName)
if err != nil {
return nil, err
}

// When we're here in the context of `interfaces sync`, the to-curl flag
// is always false (`interfaces sync` has no `--to-curl` flag)
// When we're here in the context of `trigger sync`, the to-curl flag
// is always false (`trigger sync` has no `--to-curl` flag)
// and thus the call will never exit unexpectedly
utils.MaybeCurlAndExit(getTriggerCall, astarteAPIClient)

Expand All @@ -420,6 +406,22 @@ func getTriggerDefinition(realm, triggerName string) (map[string]interface{}, er
if err != nil {
return nil, err
}
triggerDefinition, _ := rawTRigger.(map[string]interface{})
return triggerDefinition, nil

var triggerDefinition triggers.AstarteTrigger

UnmarshalledTrigger, _ := json.Marshal(rawTRigger.(map[string]interface{}))

if err := json.Unmarshal(UnmarshalledTrigger, &triggerDefinition); err != nil {
return nil, err
}

return &triggerDefinition, nil
}

func validateTrigger(path string) bool {
if _, err := triggers.ParseTriggerFrom(path); err != nil {
return false
} else {
return true
}
}
24 changes: 13 additions & 11 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
module github.com/astarte-platform/astartectl

go 1.20
go 1.21

toolchain go1.21.4

require (
code.cloudfoundry.org/bytefmt v0.0.0-20211005130812-5bb3c17173e5
github.com/Masterminds/semver/v3 v3.1.1
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
github.com/astarte-platform/astarte-go v0.91.1-0.20230718084224-d5bbb47f5179
github.com/astarte-platform/astarte-go v0.92.0
github.com/go-openapi/strfmt v0.21.1 // indirect
github.com/google/go-cmp v0.5.8
github.com/google/go-github/v30 v30.1.0
github.com/google/uuid v1.3.0
github.com/google/uuid v1.4.0
github.com/jedib0t/go-pretty v4.3.0+incompatible
github.com/mitchellh/go-homedir v1.1.0
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0
Expand All @@ -28,26 +30,26 @@ require (
cloud.google.com/go v0.99.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.18 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
github.com/cristalhq/jwt/v3 v3.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-logr/logr v1.2.0 // indirect
github.com/go-openapi/errors v0.19.8 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-querystring v1.0.0 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/iancoleman/orderedmap v0.2.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/iancoleman/orderedmap v0.3.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/magiconair/properties v1.8.5 // indirect
Expand All @@ -64,11 +66,11 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/gjson v1.17.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
go.mongodb.org/mongo-driver v1.7.5 // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sys v0.5.0 // indirect
Expand All @@ -79,7 +81,7 @@ require (
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.30.0 // indirect
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect
Expand Down
Loading

0 comments on commit 1e359d0

Please sign in to comment.