diff --git a/go.mod b/go.mod index 56318068f27..a4b32cfc5a9 100644 --- a/go.mod +++ b/go.mod @@ -138,15 +138,15 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.25.0 - golang.org/x/mod v0.19.0 - golang.org/x/net v0.27.0 + golang.org/x/crypto v0.27.0 + golang.org/x/mod v0.21.0 + golang.org/x/net v0.29.0 golang.org/x/oauth2 v0.22.0 golang.org/x/sync v0.8.0 - golang.org/x/sys v0.22.0 - golang.org/x/text v0.16.0 + golang.org/x/sys v0.25.0 + golang.org/x/text v0.18.0 golang.org/x/time v0.6.0 - golang.org/x/tools v0.23.0 + golang.org/x/tools v0.25.0 google.golang.org/api v0.191.0 google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf // indirect google.golang.org/grpc v1.64.1 @@ -209,6 +209,7 @@ require ( github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/icholy/digest v0.1.22 + github.com/meraki/dashboard-api-go/v3 v3.0.9 github.com/otiai10/copy v1.12.0 github.com/pierrec/lz4/v4 v4.1.18 github.com/pkg/xattr v0.4.9 @@ -260,6 +261,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect + github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bluekeyes/go-gitdiff v0.7.1 // indirect github.com/cespare/xxhash v1.1.0 // indirect @@ -290,6 +292,7 @@ require ( github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-resty/resty/v2 v2.11.0 // indirect github.com/go-stack/stack v1.8.0 // indirect github.com/gobuffalo/here v0.6.7 // indirect github.com/goccy/go-json v0.10.2 // indirect @@ -300,6 +303,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/licenseclassifier v0.0.0-20221004142553-c1ed8fcf4bab // indirect github.com/google/s2a-go v0.1.8 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect @@ -370,8 +374,9 @@ require ( go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/ratelimit v0.3.1 // indirect golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 // indirect - golang.org/x/term v0.22.0 // indirect + golang.org/x/term v0.24.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect gopkg.in/yaml.v3 v3.0.1 // indirect @@ -408,5 +413,6 @@ replace ( github.com/golang/glog => github.com/elastic/glog v1.0.1-0.20210831205241-7d8b5c89dfc4 github.com/google/gopacket => github.com/elastic/gopacket v1.1.20-0.20211202005954-d412fca7f83a github.com/insomniacslk/dhcp => github.com/elastic/dhcp v0.0.0-20200227161230-57ec251c7eb3 // indirect + github.com/meraki/dashboard-api-go/v3 => github.com/tommyers-elastic/dashboard-api-go/v3 v3.0.0-20240913150833-a945473a8f25 github.com/snowflakedb/gosnowflake => github.com/snowflakedb/gosnowflake v1.6.19 ) diff --git a/go.sum b/go.sum index df15b7c4791..109a30996dc 100644 --- a/go.sum +++ b/go.sum @@ -387,6 +387,8 @@ github.com/awslabs/goformation/v7 v7.14.9/go.mod h1:7obldQ8NQ/AkMsgL5K3l4lRMDFB6 github.com/awslabs/kinesis-aggregation/go/v2 v2.0.0-20220623125934-28468a6701b5 h1:lxW5Q6K2IisyF5tlr6Ts0W4POGWQZco05MJjFmoeIHs= github.com/awslabs/kinesis-aggregation/go/v2 v2.0.0-20220623125934-28468a6701b5/go.mod h1:0Qr1uMHFmHsIYMcG4T7BJ9yrJtWadhOmpABCX69dwuc= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/immutable v0.2.1/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= github.com/benbjohnson/tmpl v1.0.0/go.mod h1:igT620JFIi44B6awvU9IsDhR77IXWtFigTLil/RPdps= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -806,6 +808,8 @@ github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhO github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= +github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= +github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= github.com/go-sourcemap/sourcemap v2.1.2+incompatible h1:0b/xya7BKGhXuqFESKM4oIiRo9WOt2ebz7KxfreD6ug= github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -958,6 +962,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -1044,6 +1050,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737 github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/h2non/filetype v1.1.1 h1:xvOwnXKAckvtLWsN398qS9QhlxlnVXBjXBydK2/UFB4= github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.4.0/go.mod h1:xc8u05kyMa3Wjr9eEAsIAo3dg8+LywT5E/Cl7cNS5nU= github.com/hashicorp/consul/api v1.8.1/go.mod h1:sDjTOq0yUyv5G4h+BqSea7Fn6BU+XbolEz1952UB+mk= @@ -1629,6 +1637,8 @@ github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYa github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tommyers-elastic/dashboard-api-go/v3 v3.0.0-20240913150833-a945473a8f25 h1:o24r+NDexzdlwgqI0Dglq2I/cdONYRACikcUmYmovtQ= +github.com/tommyers-elastic/dashboard-api-go/v3 v3.0.0-20240913150833-a945473a8f25/go.mod h1:COGDRzuD05ZS/zp0lDCTDFhx6kAuuNdhDjY0y2ifi5o= github.com/tsg/go-daemon v0.0.0-20200207173439-e704b93fd89b h1:X/8hkb4rQq3+QuOxpJK7gWmAXmZucF0EI1s1BfBLq6U= github.com/tsg/go-daemon v0.0.0-20200207173439-e704b93fd89b/go.mod h1:jAqhj/JBVC1PwcLTWd6rjQyGyItxxrhpiBl8LSuAGmw= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= @@ -1770,6 +1780,8 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0= +go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= @@ -1816,11 +1828,12 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1879,8 +1892,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1958,9 +1971,10 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2110,11 +2124,12 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -2123,11 +2138,12 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2144,8 +2160,8 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2153,6 +2169,7 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -2243,8 +2260,8 @@ golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2417,6 +2434,8 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= +gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= gopkg.in/hjson/hjson-go.v3 v3.0.1/go.mod h1:X6zrTSVeImfwfZLfgQdInl9mWjqPqgH90jom9nym/lw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= diff --git a/x-pack/metricbeat/include/list.go b/x-pack/metricbeat/include/list.go index 492e4e7d4d0..b13740cfe44 100644 --- a/x-pack/metricbeat/include/list.go +++ b/x-pack/metricbeat/include/list.go @@ -48,6 +48,8 @@ import ( _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/istio/mesh" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/istio/mixer" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/istio/pilot" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/device_health" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/mssql" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/mssql/performance" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/mssql/transaction_log" diff --git a/x-pack/metricbeat/module/meraki/_meta/config.yml b/x-pack/metricbeat/module/meraki/_meta/config.yml new file mode 100644 index 00000000000..f7513df6a64 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/_meta/config.yml @@ -0,0 +1,6 @@ +- module: meraki + metricsets: ["appliance_uplinks_status_and_ha"] + enabled: false + period: 10s + hosts: ["localhost"] + diff --git a/x-pack/metricbeat/module/meraki/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/_meta/docs.asciidoc new file mode 100644 index 00000000000..f2cc90ab62f --- /dev/null +++ b/x-pack/metricbeat/module/meraki/_meta/docs.asciidoc @@ -0,0 +1,2 @@ +This is the meraki module. + diff --git a/x-pack/metricbeat/module/meraki/_meta/fields.yml b/x-pack/metricbeat/module/meraki/_meta/fields.yml new file mode 100644 index 00000000000..59b651d0fc4 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/_meta/fields.yml @@ -0,0 +1,12 @@ +- key: meraki + title: "meraki" + release: beta + description: > + Meraki module collects metrics from the meraki api + fields: + - name: meraki + type: group + description: > + Various Meraki metrics + fields: + diff --git a/x-pack/metricbeat/module/meraki/device_health/_meta/data.json b/x-pack/metricbeat/module/meraki/device_health/_meta/data.json new file mode 100644 index 00000000000..23f3ffcaf15 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/_meta/data.json @@ -0,0 +1,19 @@ +{ + "@timestamp":"2016-05-23T08:05:34.853Z", + "beat":{ + "hostname":"beathost", + "name":"beathost" + }, + "metricset":{ + "host":"localhost", + "module":"meraki", + "name":"device_health", + "rtt":44269 + }, + "meraki":{ + "device_health":{ + "example": "device_health" + } + }, + "type":"metricsets" +} diff --git a/x-pack/metricbeat/module/meraki/device_health/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/device_health/_meta/docs.asciidoc new file mode 100644 index 00000000000..f09d386197a --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/_meta/docs.asciidoc @@ -0,0 +1 @@ +This is the device_health metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/device_health/_meta/fields.yml b/x-pack/metricbeat/module/meraki/device_health/_meta/fields.yml new file mode 100644 index 00000000000..2d66e605a1b --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/_meta/fields.yml @@ -0,0 +1,15 @@ +- name: device_health + type: group + release: beta + description: > + meraki device_health metricset to collect device metrics + fields: + - name: device + type: group + description: > + meraki devices metrics + fields: + - name: serial + type: number + description: > + unique device serial diff --git a/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go b/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go new file mode 100644 index 00000000000..250725fd442 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go @@ -0,0 +1,41 @@ +package device_health + +import ( + "fmt" + "strings" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func getDevicePerformanceScores(client *meraki_api.Client, devices map[Serial]*Device) (map[Serial]*DevicePerformanceScore, error) { + + mx_devices := pruneDevicesForMxOnly(devices) + + scores := make(map[Serial]*DevicePerformanceScore) + for _, device := range mx_devices { + + score_val, score_res, score_err := client.Appliance.GetDeviceAppliancePerformance(device.Serial) + if score_err != nil { + return nil, fmt.Errorf("Appliance.GetDeviceAppliancePerformance failed; %w", score_err) + } + + if score_res.StatusCode() != 204 { + scores[Serial(device.Serial)] = &DevicePerformanceScore{ + PerformanceScore: *score_val.PerfScore, + } + } + } + + return scores, nil +} + +func pruneDevicesForMxOnly(devices map[Serial]*Device) map[Serial]*Device { + + mx_devices := make(map[Serial]*Device) + for k, v := range devices { + if strings.Index(v.Model, "MX") == 0 { + mx_devices[k] = v + } + } + return mx_devices +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_appliance_uplink_status_and_ha.go b/x-pack/metricbeat/module/meraki/device_health/device_appliance_uplink_status_and_ha.go new file mode 100644 index 00000000000..2c90fe03e99 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_appliance_uplink_status_and_ha.go @@ -0,0 +1,82 @@ +package device_health + +import ( + "fmt" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func reportApplianceUplinkStatuses(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, responseApplianceUplinkStatuses *meraki_api.ResponseApplianceGetOrganizationApplianceUplinkStatuses, lossLatencyUplinks []*Uplink) { + + metrics := []mapstr.M{} + + for _, uplink := range *responseApplianceUplinkStatuses { + + if device, ok := devices[Serial(uplink.Serial)]; ok { + metric := mapstr.M{ + "uplink.high_availablity.enabled": uplink.HighAvailability.Enabled, + "uplink.high_availablity.role": uplink.HighAvailability.Role, + "uplink.last_reported_at": uplink.LastReportedAt, + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, + } + + for k, v := range device.Details { + metric[fmt.Sprintf("device.details.%s", k)] = v + } + + for _, item := range *uplink.Uplinks { + + uplink_encountered := false + + metric["uplink.interface"] = item.Interface + metric["uplink.status"] = item.Status + metric["uplink.ip"] = item.IP + metric["uplink.gateway"] = item.Gateway + metric["uplink.public_ip"] = item.PublicIP + metric["uplink.primary_dns"] = item.PrimaryDNS + metric["uplink.secondary_dns"] = item.SecondaryDNS + metric["uplink.ip_assigned_by"] = item.IPAssignedBy + + for _, lossLatencyMetric := range lossLatencyUplinks { + if lossLatencyMetric.Interface == item.Interface && string(lossLatencyMetric.DeviceSerial) == device.Serial && lossLatencyMetric.NetworkID == device.NetworkID { + for _, lossLatency := range lossLatencyMetric.Metrics { + + uplink_encountered = true + //It seems there is bug in the client.Organizations.GetOrganizationDevicesUplinksLossAndLatency code returning differnt IP + //To mitigate, I am additionally printing the ip as seperate value, IMO it is odd these do not match. + // client.Appliance.GetOrganizationApplianceUplinkStatuses + metrics = append(metrics, mapstr.Union(metric, mapstr.M{ + "uplink.loss_latancy.ip": lossLatencyMetric.IP, + "@timestamp": lossLatency.Timestamp, + "uplink.loss_latancy.loss_percent": lossLatency.LossPercent, + "uplink.loss_latancy.latency_ms": lossLatency.LatencyMs, + })) + } + } + + } + if !uplink_encountered { + metrics = append(metrics, metric) + } + + } + } + } + + ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_cellular_gateway_uplink_status.go b/x-pack/metricbeat/module/meraki/device_health/device_cellular_gateway_uplink_status.go new file mode 100644 index 00000000000..ea37f2b6004 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_cellular_gateway_uplink_status.go @@ -0,0 +1,58 @@ +package device_health + +import ( + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func reportCellularGatewayApplianceUplinkStatuses(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, responseCellularGatewayUplinkStatuses *meraki_api.ResponseCellularGatewayGetOrganizationCellularGatewayUplinkStatuses) { + + metrics := []mapstr.M{} + + for _, uplink := range *responseCellularGatewayUplinkStatuses { + + if device, ok := devices[Serial(uplink.Serial)]; ok { + metric := mapstr.M{ + "cellular.gateway.uplink.network_id": uplink.NetworkID, + "cellular.gateway.uplink.last_reported_at": uplink.LastReportedAt, + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, + } + + for _, item := range *uplink.Uplinks { + metrics = append(metrics, mapstr.Union(metric, mapstr.M{ + "cellular.gateway.uplink.apn": item.Apn, + "cellular.gateway.uplink.connection_type": item.ConnectionType, + "cellular.gateway.uplink.dns1": item.DNS1, + "cellular.gateway.uplink.dns2": item.DNS2, + "cellular.gateway.uplink.gateway": item.Gateway, + "cellular.gateway.uplink.iccid": item.Iccid, + "cellular.gateway.uplink.interface": item.Interface, + "cellular.gateway.uplink.ip": item.IP, + "cellular.gateway.uplink.model": item.Model, + "cellular.gateway.uplink.provider": item.Provider, + "cellular.gateway.uplink.public_ip": item.PublicIP, + "cellular.gateway.uplink.signal_stat.rsrp": item.SignalStat.Rsrp, + "cellular.gateway.uplink.signal_stat.rsrq": item.SignalStat.Rsrq, + "cellular.gateway.uplink.signal_type": item.SignalType, + "cellular.gateway.uplink.status": item.Status, + })) + + } + } + } + ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_health.go b/x-pack/metricbeat/module/meraki/device_health/device_health.go new file mode 100644 index 00000000000..2ad36c8bde0 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_health.go @@ -0,0 +1,233 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package device_health + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + "github.com/elastic/beats/v7/metricbeat/mb" + + "github.com/elastic/elastic-agent-libs/logp" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +// init registers the MetricSet with the central registry as soon as the program +// starts. The New function will be called later to instantiate an instance of +// the MetricSet for each host is defined in the module's configuration. After the +// MetricSet has been created then Fetch will begin to be called periodically. +func init() { + + mb.Registry.MustAddMetricSet("meraki", "device_health", New) + +} + +type config struct { + BaseURL string `config:"apiBaseURL"` + ApiKey string `config:"apiKey"` + DebugMode string `config:"apiDebugMode"` + Organizations []string `config:"organizations"` + // todo: device filtering? +} + +func defaultConfig() *config { + return &config{ + BaseURL: "https://api.meraki.com", + DebugMode: "false", + } +} + +// MetricSet holds any configuration or state information. It must implement +// the mb.MetricSet interface. And this is best achieved by embedding +// mb.BaseMetricSet because it implements all of the required mb.MetricSet +// interface methods except for Fetch. +type MetricSet struct { + mb.BaseMetricSet + logger *logp.Logger + client *meraki_api.Client + organizations []string + meraki_url string + meraki_apikey string +} + +// New creates a new instance of the MetricSet. New is responsible for unpacking +// any MetricSet specific configuration options if there are any. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + cfgwarn.Beta("The meraki device_health metricset is beta.") + + logger := logp.NewLogger(base.FullyQualifiedName()) + + config := defaultConfig() + if err := base.Module().UnpackConfig(config); err != nil { + return nil, err + } + + logger.Debugf("loaded config: %v", config) + client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") + if err != nil { + logger.Error("creating Meraki dashboard API client failed: %w", err) + return nil, err + } + + return &MetricSet{ + BaseMetricSet: base, + logger: logger, + client: client, + organizations: config.Organizations, + meraki_url: config.BaseURL, + meraki_apikey: config.ApiKey, + }, nil +} + +// Fetch method implements the data gathering and data conversion to the right +// format. It publishes the event which is then forwarded to the output. In case +// of an error set the Error field of mb.Event or simply call report.Error(). +func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { + + for _, org := range m.organizations { + + //Get Devices + devices, err := GetDevices(m.client, org) + if err != nil { + return fmt.Errorf("getDevices() failed; %w", err) + } + + //Get & Report Device Status + deviceStatuses, err := getDeviceStatuses(m.client, org) + if err != nil { + return fmt.Errorf("getDeviceStatuses() failed; %w", err) + } + + //Get mx device performance score + mx_scores, err := getDevicePerformanceScores(m.client, devices) + if err != nil { + return fmt.Errorf("getDevicePerformanceScores() failed; %w", err) + } + reportDeviceStatusMetrics(reporter, org, devices, deviceStatuses, mx_scores) + + // //Get & Report Organization Appliance Uplink + appliance_val, appliance_res, appliance_err := m.client.Appliance.GetOrganizationApplianceUplinkStatuses(org, &meraki_api.GetOrganizationApplianceUplinkStatusesQueryParams{}) + if appliance_err != nil { + return fmt.Errorf("Appliance.GetOrganizationApplianceUplinkStatuses failed; [%d] %s. %w", appliance_res.StatusCode(), appliance_res.Body(), appliance_err) + } + //Get & Report Device Uplink Status + lossLatencyuplinks, err := getDeviceUplinkLossLatencyMetrics(m.client, org, m.BaseMetricSet.Module().Config().Period) + if err != nil { + return fmt.Errorf("getDeviceUplinkMetrics() failed; %w", err) + } + reportApplianceUplinkStatuses(reporter, org, devices, appliance_val, lossLatencyuplinks) + + //Get & Report Device Uplink Status + uplinks, err := getDeviceUplinkMetrics(m.client, org, m.BaseMetricSet.Module().Config().Period) + if err != nil { + return fmt.Errorf("getDeviceUplinkMetrics() failed; %w", err) + } + reportDeviceUplinkMetrics(reporter, org, devices, uplinks) + + //Get & Report Device License State + cotermLicenses, perDeviceLicenses, systemsManagerLicense, err := getLicenseStates(m.client, org) + if err != nil { + return fmt.Errorf("getLicenseStates() failed; %w", err) + } + reportLicenseMetrics(reporter, org, cotermLicenses, perDeviceLicenses, systemsManagerLicense) + + //Get & Report Org Celluar Uplink Status + cullular_val, cullular_res, cullular_err := m.client.CellularGateway.GetOrganizationCellularGatewayUplinkStatuses(org, &meraki_api.GetOrganizationCellularGatewayUplinkStatusesQueryParams{}) + if cullular_err != nil { + return fmt.Errorf("CellularGateway.GetOrganizationCellularGatewayUplinkStatuses failed; [%d] %s. %w", cullular_res.StatusCode(), cullular_res.Body(), cullular_err) + } + reportCellularGatewayApplianceUplinkStatuses(reporter, org, devices, cullular_val) + + //Get Org Networks + //Get Network Health by Org Network + //Report NetworkHalthChannelUtilization + orgNetworks, orgNetwork_res, orgNetwork_err := m.client.Organizations.GetOrganizationNetworks(org, &meraki_api.GetOrganizationNetworksQueryParams{}) + if orgNetwork_err != nil { + return fmt.Errorf("Organizations.GetOrganizationNetworks failed; [%d] %s. %w", orgNetwork_res.StatusCode(), orgNetwork_res.Body(), orgNetwork_err) + } + networkHealthUtilizations, err := getNetworkHealthChannelUtilization(m.client, orgNetworks) + if err != nil { + return err + } + reportNetworkHealthChannelUtilization(reporter, org, devices, networkHealthUtilizations) + + // Get and Report Organization Wireless Devices Channel Utilization + wireless_res, wireless_err := m.client.Devices.GetOrganizationWirelessDevicesChannelUtilizationByDevice(org, &meraki_api.GetOrganizationWirelessDevicesChannelUtilizationByDeviceQueryParams{}) + if wireless_err != nil { + return fmt.Errorf("GetOrganizationWirelessDevicesChannelUtilizationByDevice failed; [%d] %s. %w", wireless_res.StatusCode(), wireless_res.Body(), wireless_err) + } + var wirelessDevices *meraki_api.ResponseOrganizationsGetOrganizationWirelessDevicesChannelUtilizationByDevice + unmashal_err := json.Unmarshal(wireless_res.Body(), &wirelessDevices) + if unmashal_err != nil { + return fmt.Errorf("device_network_health_channel_utilization json umarshal failed; %w", unmashal_err) + } + reportWirelessDeviceChannelUtilization(reporter, org, devices, wirelessDevices) + + //Use Org Networks Retrieved above + //Get VPN site_to_site + //Report VPN site_to_site + networkVPNSiteToSites, err := getNetworkApplianceVPNSiteToSite(m.client, orgNetworks) + if err != nil { + return err + } + reportNetwrokApplianceVPNSiteToSite(reporter, org, devices, networkVPNSiteToSites) + + //Get & Report Organization License by Device + license_val, license_res, license_err := m.client.Organizations.GetOrganizationLicenses(org, &meraki_api.GetOrganizationLicensesQueryParams{}) + if license_err != nil { + return fmt.Errorf("Organizations.GetOrganizationLicenses failed; [%d] %s. %w", license_res.StatusCode(), license_res.Body(), license_err) + } + reportOrganizationDeviceLicenses(reporter, org, devices, license_val) + + //Use Org Networks Retrieved above + //Get Network Ports + //Report Network Ports By Device + networkPorts, err := getNetworkAppliancePorts(m.client, orgNetworks) + if err != nil { + return err + } + reportNetwrokAppliancePorts(reporter, org, devices, networkPorts) + + //Get & Report Organization License by Device + switchPorts_val, switchPorts_res, switchPorts_err := m.client.Switch.GetOrganizationSwitchPortsBySwitch(org, &meraki_api.GetOrganizationSwitchPortsBySwitchQueryParams{}) + if switchPorts_err != nil { + return fmt.Errorf("Switch.GetOrganizationSwitchPortsBySwitch failed; [%d] %s. %w", switchPorts_res.StatusCode(), switchPorts_res.Body(), switchPorts_err) + } + reportOrganizationDeviceSwitchPortBySwitch(reporter, org, devices, switchPorts_val) + + //Use Org Networks Retrieved above + //Get Network Ports + //Report Network Ports By Device + switchPortStatusBySerials, err := getSwitchPortStatusBySerial(m.client, org) + if err != nil { + return err + } + reportSwitchPortStatusBySerial(reporter, org, devices, switchPortStatusBySerials) + + } + + return nil +} + +func ReportMetricsForOrganization(reporter mb.ReporterV2, organizationID string, metrics ...[]mapstr.M) { + + for _, metricSlice := range metrics { + for _, metric := range metricSlice { + event := mb.Event{ModuleFields: mapstr.M{"organization_id": organizationID}} + if ts, ok := metric["@timestamp"].(time.Time); ok { + event.Timestamp = ts + delete(metric, "@timestamp") + } + + event.ModuleFields.Update(metric) + + reporter.Event(event) + } + } +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_info.go b/x-pack/metricbeat/module/meraki/device_health/device_info.go new file mode 100644 index 00000000000..a57b439f33b --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_info.go @@ -0,0 +1,45 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package device_health + +import ( + "fmt" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func GetDevices(client *meraki_api.Client, organizationID string) (map[Serial]*Device, error) { + val, res, err := client.Organizations.GetOrganizationDevices(organizationID, &meraki_api.GetOrganizationDevicesQueryParams{}) + + if err != nil { + return nil, fmt.Errorf("GetOrganizationDevices failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + devices := make(map[Serial]*Device) + for _, d := range *val { + device := Device{ + Firmware: d.Firmware, + Imei: d.Imei, + LanIP: d.LanIP, + Location: []*float64{d.Lng, d.Lat}, // (lon, lat) order is important! + Mac: d.Mac, + Model: d.Model, + Name: d.Name, + NetworkID: d.NetworkID, + Notes: d.Notes, + ProductType: d.ProductType, + Serial: d.Serial, + Tags: d.Tags, + } + if d.Details != nil { + for _, detail := range *d.Details { + device.Details[detail.Name] = detail.Value + } + } + devices[Serial(device.Serial)] = &device + } + + return devices, nil +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_license.go b/x-pack/metricbeat/module/meraki/device_health/device_license.go new file mode 100644 index 00000000000..bdb398cd938 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_license.go @@ -0,0 +1,53 @@ +package device_health + +import ( + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func reportOrganizationDeviceLicenses(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, orgLicenseBySerial *meraki_api.ResponseOrganizationsGetOrganizationLicenses) { + + metrics := []mapstr.M{} + + for _, license := range *orgLicenseBySerial { + + if device, ok := devices[Serial(license.DeviceSerial)]; ok { + metric := mapstr.M{ + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, + "device.license.activation_date": license.ActivationDate, + "device.license.claim_date": license.ClaimDate, + "device.license.device_serial": license.DeviceSerial, + "device.license.duration_in_days": license.DurationInDays, + "device.license.expiration_date": license.ExpirationDate, + "device.license.head_license_id": license.HeadLicenseID, + "device.license.id": license.ID, + "device.license.license_type": license.LicenseType, + "device.license.network_id": license.NetworkID, + "device.license.order_number": license.OrderNumber, + "device.license.seat_count": license.SeatCount, + "device.license.state": license.State, + "device.license.total_duration_in_days": license.TotalDurationInDays, + //"device.license.license_key": license.LicenseKey, Not sure we want private key information on metric data in elastic + //"device.license.11": license.PermanentlyQueuedLicenses, //DEPRECATED List of permanently queued licenses attached to the license. Instead, use /organizations/{organizationId}/licenses?deviceSerial= to retrieved queued licenses for a given device. + } + + metrics = append(metrics, metric) + } + } + + ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_ports.go b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_ports.go new file mode 100644 index 00000000000..ac023f4e329 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_ports.go @@ -0,0 +1,76 @@ +package device_health + +import ( + "fmt" + "strings" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func getNetworkAppliancePorts(client *meraki_api.Client, networks *meraki_api.ResponseOrganizationsGetOrganizationNetworks) (map[NetworkID]*meraki_api.ResponseApplianceGetNetworkAppliancePorts, error) { + + networkPorts := make(map[NetworkID]*meraki_api.ResponseApplianceGetNetworkAppliancePorts) + + for _, network := range *networks { + + networkPort, res, err := client.Appliance.GetNetworkAppliancePorts(network.ID) + if err != nil { + //Error: "This endpoint only supports MX networks" or "VLANs are not enabled for this network" + // We just ignore theses error but do not append to the list + if !(strings.Contains(string(res.Body()), "VLANs are not enabled")) && !(strings.Contains(string(res.Body()), "MX networks")) { + //Any other problem we are going to return an error + return nil, fmt.Errorf("Appliance.GetNetworkAppliancePorts failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + } else { + networkPorts[NetworkID(network.ID)] = networkPort + + } + + } + + return networkPorts, nil +} + +func reportNetwrokAppliancePorts(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, networkPorts map[NetworkID]*meraki_api.ResponseApplianceGetNetworkAppliancePorts) { + metrics := []mapstr.M{} + + for network_id, networkPort := range networkPorts { + for _, device := range devices { + if device.NetworkID == string(network_id) { + metric := mapstr.M{ + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, + } + + for _, port := range *networkPort { + metric["appliance.network.port.number"] = port.Number + metric["appliance.network.port.enabled"] = port.Enabled + metric["appliance.network.port.type"] = port.Type + metric["appliance.network.port.drop_untagged_traffic"] = port.DropUntaggedTraffic + metric["appliance.network.port.vlan"] = port.VLAN + metric["appliance.network.port.allowed_vlans"] = port.AllowedVLANs + metric["appliance.network.port.access_policy"] = port.AccessPolicy + metrics = append(metrics, metric) + } + + } + } + + } + + ReportMetricsForOrganization(reporter, organizationID, metrics) + +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go new file mode 100644 index 00000000000..031095452b1 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go @@ -0,0 +1,92 @@ +package device_health + +import ( + "fmt" + "strings" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func getNetworkApplianceVPNSiteToSite(client *meraki_api.Client, networks *meraki_api.ResponseOrganizationsGetOrganizationNetworks) (map[NetworkID]*meraki_api.ResponseApplianceGetNetworkApplianceVpnSiteToSiteVpn, error) { + + networkVPNSiteToSites := make(map[NetworkID]*meraki_api.ResponseApplianceGetNetworkApplianceVpnSiteToSiteVpn) + + for _, network := range *networks { + + networkVPNSiteToSite, res, err := client.Appliance.GetNetworkApplianceVpnSiteToSiteVpn(network.ID) + if err != nil { + //Error: "This endpoint only supports MX networks" + // We just ignore this error but do not append to the list + if !(strings.Contains(string(res.Body()), "MX network")) { + //Any other problem we are going to return an error + return nil, fmt.Errorf("Appliance.GetNetworkApplianceVpnSiteToSiteVpn failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + } else { + networkVPNSiteToSites[NetworkID(network.ID)] = networkVPNSiteToSite + + } + + } + + return networkVPNSiteToSites, nil +} + +func reportNetwrokApplianceVPNSiteToSite(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, networkVPNSiteToSites map[NetworkID]*meraki_api.ResponseApplianceGetNetworkApplianceVpnSiteToSiteVpn) { + metrics := []mapstr.M{} + + for network_id, networkVPNSiteToSite := range networkVPNSiteToSites { + for _, device := range devices { + networkSiteToSiteMode_encountered := false + networkSiteToSiteSubnet_encountered := false + if device.NetworkID == string(network_id) { + metric := mapstr.M{ + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, + } + metric["network.appliance.vpn.site_to_site.network_id"] = network_id + metric["network.appliance.vpn.site_to_site.mode"] = networkVPNSiteToSite.Mode + + if networkVPNSiteToSite.Mode != "none" { + if networkVPNSiteToSite.Hubs != nil { + for _, hub := range *networkVPNSiteToSite.Hubs { + metric["network.appliance.vpn.site_to_site.hub.hub_id"] = hub.HubID + metric["network.appliance.vpn.site_to_site.hub.use_default_route"] = *hub.UseDefaultRoute + } + networkSiteToSiteMode_encountered = true + metrics = append(metrics, metric) + } + + if networkVPNSiteToSite.Subnets != nil { + for _, subnet := range *networkVPNSiteToSite.Subnets { + metric["network.appliance.vpn.site_to_site.subnet.local_subnet"] = subnet.LocalSubnet + metric["network.appliance.vpn.site_to_site.subnet.use_vpn"] = *subnet.UseVpn + } + networkSiteToSiteSubnet_encountered = true + metrics = append(metrics, metric) + } + } + if !networkSiteToSiteMode_encountered && !networkSiteToSiteSubnet_encountered { + metrics = append(metrics, metric) + } + + } + } + + } + + ReportMetricsForOrganization(reporter, organizationID, metrics) + +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go b/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go new file mode 100644 index 00000000000..feb179d5956 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go @@ -0,0 +1,95 @@ +package device_health + +import ( + "fmt" + "strings" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func getNetworkHealthChannelUtilization(client *meraki_api.Client, networks *meraki_api.ResponseOrganizationsGetOrganizationNetworks) ([]*meraki_api.ResponseNetworksGetNetworkNetworkHealthChannelUtilization, error) { + + var networkHealthUtilizations []*meraki_api.ResponseNetworksGetNetworkNetworkHealthChannelUtilization + + for _, network := range *networks { + for _, product_type := range network.ProductTypes { + if product_type == "wireless" { + networkHealthUtilization, res, err := client.Networks.GetNetworkNetworkHealthChannelUtilization(network.ID, &meraki_api.GetNetworkNetworkHealthChannelUtilizationQueryParams{}) + if err != nil { + //"This endpoint is only available for networks on MR 27.0 or above." + // We just ignore this error but do not append to the list + if !(strings.Contains(string(res.Body()), "MR 27.0")) { + //Any other problem we are going to return an error + return nil, fmt.Errorf("Networks.GetNetworkNetworkHealthChannelUtilization failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + } else { + networkHealthUtilizations = append(networkHealthUtilizations, networkHealthUtilization) + } + + } + } + } + return networkHealthUtilizations, nil +} + +func reportNetworkHealthChannelUtilization(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, networkHealthUtilizations []*meraki_api.ResponseNetworksGetNetworkNetworkHealthChannelUtilization) { + metrics := []mapstr.M{} + for _, networkHealthUtil := range networkHealthUtilizations { + for _, network := range *networkHealthUtil { + + wifi0_encountered := false + wifi1_encountered := false + + metric := mapstr.M{ + "network.health.channel.radio.serial": network.Serial, + "network.health.channel.radio.model": network.Model, + "network.health.channel.radio.tags": network.Tags, + } + + if device, ok := devices[Serial(network.Serial)]; ok { + metric["device.address"] = device.Address + metric["device.firmware"] = device.Firmware + metric["device.imei"] = device.Imei + metric["device.lan_ip"] = device.LanIP + metric["device.location"] = device.Location + metric["device.mac"] = device.Mac + metric["device.model"] = device.Model + metric["device.name"] = device.Name + metric["device.network_id"] = device.NetworkID + metric["device.notes"] = device.Notes + metric["device.product_type"] = device.ProductType + metric["device.serial"] = device.Serial + metric["device.tags"] = device.Tags + + } + + for _, wifi0 := range *network.Wifi0 { + metric["network.health.channel.radio.wifi0.start_time"] = wifi0.StartTime + metric["network.health.channel.radio.wifi0.end_time"] = wifi0.EndTime + metric["network.health.channel.radio.wifi0.utilization80211"] = wifi0.Utilization80211 + metric["network.health.channel.radio.wifi0.utilizationNon80211"] = wifi0.UtilizationNon80211 + metric["network.health.channel.radio.wifi0.utilizationTotal"] = wifi0.UtilizationTotal + wifi0_encountered = true + metrics = append(metrics, metric) + } + + for _, wifi1 := range *network.Wifi1 { + metric["network.health.channel.radio.wifi1.start_time"] = wifi1.StartTime + metric["network.health.channel.radio.wifi1.end_time"] = wifi1.EndTime + metric["network.health.channel.radio.wifi1.utilization80211"] = wifi1.Utilization80211 + metric["network.health.channel.radio.wifi1.utilizationNon80211"] = wifi1.UtilizationNon80211 + metric["network.health.channel.radio.wifi1.utilizationTotal"] = wifi1.UtilizationTotal + wifi1_encountered = true + metrics = append(metrics, metric) + } + + if !wifi0_encountered && !wifi1_encountered { + metrics = append(metrics, metric) + } + + } + } + ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_status.go b/x-pack/metricbeat/module/meraki/device_health/device_status.go new file mode 100644 index 00000000000..14ef0807b58 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_status.go @@ -0,0 +1,80 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package device_health + +import ( + "fmt" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func getDeviceStatuses(client *meraki_api.Client, organizationID string) (map[Serial]*DeviceStatus, error) { + val, res, err := client.Organizations.GetOrganizationDevicesStatuses(organizationID, &meraki_api.GetOrganizationDevicesStatusesQueryParams{}) + + if err != nil { + return nil, fmt.Errorf("GetOrganizationDevicesStatuses failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + statuses := make(map[Serial]*DeviceStatus) + for _, status := range *val { + statuses[Serial(status.Serial)] = &DeviceStatus{ + Gateway: status.Gateway, + IPType: status.IPType, + LastReportedAt: status.LastReportedAt, + PrimaryDNS: status.PrimaryDNS, + PublicIP: status.PublicIP, + SecondaryDNS: status.SecondaryDNS, + Status: status.Status, + } + } + + return statuses, nil +} + +func reportDeviceStatusMetrics(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, deviceStatuses map[Serial]*DeviceStatus, devicePerformanceScores map[Serial]*DevicePerformanceScore) { + deviceStatusMetrics := []mapstr.M{} + for serial, device := range devices { + metric := mapstr.M{ + "device.serial": device.Serial, + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.tags": device.Tags, + } + + for k, v := range device.Details { + metric[fmt.Sprintf("device.details.%s", k)] = v + } + + if status, ok := deviceStatuses[serial]; ok { + metric["device.status.gateway"] = status.Gateway + metric["device.status.ip_type"] = status.IPType + metric["device.status.last_reported_at"] = status.LastReportedAt + metric["device.status.primary_dns"] = status.PrimaryDNS + metric["device.status.public_ip"] = status.PublicIP + metric["device.status.secondary_dns"] = status.SecondaryDNS + metric["device.status.value"] = status.Status + } + + if score, ok := devicePerformanceScores[serial]; ok { + metric["device.performance.score"] = score.PerformanceScore + } + + deviceStatusMetrics = append(deviceStatusMetrics, metric) + } + + ReportMetricsForOrganization(reporter, organizationID, deviceStatusMetrics) + +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_switch_port.go b/x-pack/metricbeat/module/meraki/device_health/device_switch_port.go new file mode 100644 index 00000000000..d77abb99249 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_switch_port.go @@ -0,0 +1,70 @@ +package device_health + +import ( + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func reportOrganizationDeviceSwitchPortBySwitch(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, orgSwitchPortsBySwitch *meraki_api.ResponseSwitchGetOrganizationSwitchPortsBySwitch) { + + metrics := []mapstr.M{} + + for _, switchPort := range *orgSwitchPortsBySwitch { + + port_encountered := false + + metric := mapstr.M{ + "switch.port.name": switchPort.Name, + "switch.port.serial": switchPort.Serial, + "switch.port.mac": switchPort.Mac, + "switch.port.network.name": switchPort.Network.Name, + "switch.port.network.id": switchPort.Network.ID, + "switch.port.model": switchPort.Model, + } + + if device, ok := devices[Serial(switchPort.Serial)]; ok { + metric["device.address"] = device.Address + metric["device.firmware"] = device.Firmware + metric["device.imei"] = device.Imei + metric["device.lan_ip"] = device.LanIP + metric["device.location"] = device.Location + metric["device.mac"] = device.Mac + metric["device.model"] = device.Model + metric["device.name"] = device.Name + metric["device.network_id"] = device.NetworkID + metric["device.notes"] = device.Notes + metric["device.product_type"] = device.ProductType + metric["device.serial"] = device.Serial + metric["device.tags"] = device.Tags + + } + + for _, port := range *switchPort.Ports { + metric["switch.port.port_id"] = port.PortID + metric["switch.port.name"] = port.Name + metric["switch.port.tags"] = port.Tags + metric["switch.port.enabled"] = port.Enabled + metric["switch.port.poe_enabled"] = port.PoeEnabled + metric["switch.port.vlan"] = port.VLAN + metric["switch.port.voice_vlan"] = port.VoiceVLAN + metric["switch.port.allowed_vlans"] = port.AllowedVLANs + metric["switch.port.rstp_enabled"] = port.RstpEnabled + metric["switch.port.stp_guard"] = port.StpGuard + metric["switch.port.link_negotiation"] = port.LinkNegotiation + metric["switch.port.access_policy_type"] = port.AccessPolicyType + metric["switch.port.sticky_mac_allow_list"] = port.StickyMacAllowList + metric["switch.port.sticky_mac_allow_list_limit"] = port.StickyMacAllowListLimit + port_encountered = true + metrics = append(metrics, metric) + } + + if !port_encountered { + metrics = append(metrics, metric) + } + + } + + ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_switch_port_status.go b/x-pack/metricbeat/module/meraki/device_health/device_switch_port_status.go new file mode 100644 index 00000000000..82f7e49100f --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_switch_port_status.go @@ -0,0 +1,117 @@ +package device_health + +import ( + "fmt" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func getSwitchPortStatusBySerial(client *meraki_api.Client, org string) (map[Serial]*meraki_api.ResponseSwitchGetDeviceSwitchPortsStatuses, error) { + + switchSerials_val, switchSerials_res, switchSerials_err := client.Switch.GetOrganizationSwitchPortsBySwitch(org, &meraki_api.GetOrganizationSwitchPortsBySwitchQueryParams{}) + if switchSerials_err != nil { + return nil, fmt.Errorf("Switch.GetOrganizationSwitchPortsBySwitch failed; [%d] %s. %w", switchSerials_res.StatusCode(), switchSerials_res.Body(), switchSerials_err) + } + + var switchSerials []string + for _, switchSerial := range *switchSerials_val { + switchSerials = append(switchSerials, switchSerial.Serial) + } + + serialSwitchPortStatuses := make(map[Serial]*meraki_api.ResponseSwitchGetDeviceSwitchPortsStatuses) + for _, serial := range switchSerials { + switchPortStatuses_val, switchPortStatuses_res, switchPortStatuses_err := client.Switch.GetDeviceSwitchPortsStatuses(serial, &meraki_api.GetDeviceSwitchPortsStatusesQueryParams{}) + if switchPortStatuses_err != nil { + return nil, fmt.Errorf("Switch.GetOrganizationSwitchPortsBySwitch failed; [%d] %s. %w", switchPortStatuses_res.StatusCode(), switchPortStatuses_res.Body(), switchPortStatuses_err) + } + serialSwitchPortStatuses[Serial(serial)] = switchPortStatuses_val + + } + + return serialSwitchPortStatuses, nil +} + +func reportSwitchPortStatusBySerial(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, switchSerialPortStatuses map[Serial]*meraki_api.ResponseSwitchGetDeviceSwitchPortsStatuses) { + metrics := []mapstr.M{} + + for serial, portStatuses := range switchSerialPortStatuses { + for _, portStatus := range *portStatuses { + metric := mapstr.M{ + "switch.port.status.port_id": portStatus.PortID, + "switch.port.status.enabled": portStatus.Enabled, + "switch.port.status.status": portStatus.Status, + "switch.port.status.spanning_tree.statuses": portStatus.SpanningTree.Statuses, + "switch.port.status.is_uplink": portStatus.IsUplink, + "switch.port.status.errors": portStatus.Errors, + "switch.port.status.warnings": portStatus.Warnings, + "switch.port.status.speed": portStatus.Speed, + "switch.port.status.duplex": portStatus.Duplex, + "switch.port.status.usage_in_kb.sent": portStatus.UsageInKb.Sent, + "switch.port.status.usage_in_kb.recv": portStatus.UsageInKb.Recv, + "switch.port.status.usage_in_kb.total": portStatus.UsageInKb.Total, + "switch.port.status.client_count": portStatus.ClientCount, + "switch.port.status.power_usage_in_wh": portStatus.PowerUsageInWh, + "switch.port.status.traffic_in_kbps.total": portStatus.TrafficInKbps.Total, + "switch.port.status.traffic_in_kbps.sent": portStatus.TrafficInKbps.Sent, + "switch.port.status.traffic_in_kbps.recv": portStatus.TrafficInKbps.Recv, + "switch.port.status.secure_port.enabled": portStatus.SecurePort.Enabled, + "switch.port.status.secure_port.active": portStatus.SecurePort.Active, + "switch.port.status.secure_port.authentication_status": portStatus.SecurePort.AuthenticationStatus, + "switch.port.status.secure_port.config_overrides.type": portStatus.SecurePort.ConfigOverrides.Type, + "switch.port.status.secure_port.config_overrides.vlan": portStatus.SecurePort.ConfigOverrides.VLAN, + "switch.port.status.secure_port.config_overrides.voice_vlan": portStatus.SecurePort.ConfigOverrides.VoiceVLAN, + "switch.port.status.secure_port.config_overrides.allowed_vlans": portStatus.SecurePort.ConfigOverrides.AllowedVLANs, + // "switch.port.status.44": portStatus.poe.isAllocated // Missing on meraki go api + } + + if portStatus.Cdp != nil { + metric["switch.port.status.cdp.system_name"] = portStatus.Cdp.SystemName + metric["switch.port.status.cdp.platform"] = portStatus.Cdp.Platform + metric["switch.port.status.cdp.device_id"] = portStatus.Cdp.DeviceID + metric["switch.port.status.cdp.port_id"] = portStatus.Cdp.PortID + metric["switch.port.status.cdp.native_vlan"] = portStatus.Cdp.NativeVLAN + metric["switch.port.status.cdp.address"] = portStatus.Cdp.Address + metric["switch.port.status.cdp.management_address"] = portStatus.Cdp.ManagementAddress + metric["switch.port.status.cdp.version"] = portStatus.Cdp.Version + metric["switch.port.status.cdp.vtp_management_domain"] = portStatus.Cdp.VtpManagementDomain + metric["switch.port.status.cdp.capabilities"] = portStatus.Cdp.Capabilities + } + + if portStatus.Lldp != nil { + metric["switch.port.status.Lldp.system_name"] = portStatus.Lldp.SystemName + metric["switch.port.status.Lldp.system_description"] = portStatus.Lldp.SystemDescription + metric["switch.port.status.Lldp.chassis_id"] = portStatus.Lldp.ChassisID + metric["switch.port.status.Lldp.port_id"] = portStatus.Lldp.PortID + metric["switch.port.status.Lldp.management_vlan"] = portStatus.Lldp.ManagementVLAN + metric["switch.port.status.Lldp.port_vlan"] = portStatus.Lldp.PortVLAN + metric["switch.port.status.Lldp.management_address"] = portStatus.Lldp.ManagementAddress + metric["switch.port.status.Lldp.port_description"] = portStatus.Lldp.PortDescription + metric["switch.port.status.Lldp.system_capabilties"] = portStatus.Lldp.SystemCapabilities + } + + if device, ok := devices[Serial(serial)]; ok { + metric["device.address"] = device.Address + metric["device.firmware"] = device.Firmware + metric["device.imei"] = device.Imei + metric["device.lan_ip"] = device.LanIP + metric["device.location"] = device.Location + metric["device.mac"] = device.Mac + metric["device.model"] = device.Model + metric["device.name"] = device.Name + metric["device.network_id"] = device.NetworkID + metric["device.notes"] = device.Notes + metric["device.product_type"] = device.ProductType + metric["device.serial"] = device.Serial + metric["device.tags"] = device.Tags + + } + metrics = append(metrics, metric) + + } + } + + ReportMetricsForOrganization(reporter, organizationID, metrics) + +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go b/x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go new file mode 100644 index 00000000000..45d00cfd158 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go @@ -0,0 +1,151 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package device_health + +import ( + "fmt" + "time" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func getDeviceUplinkLossLatencyMetrics(client *meraki_api.Client, organizationID string, period time.Duration) ([]*Uplink, error) { + val, res, err := client.Organizations.GetOrganizationDevicesUplinksLossAndLatency( + organizationID, + &meraki_api.GetOrganizationDevicesUplinksLossAndLatencyQueryParams{ + Timespan: period.Seconds() + 10, // slightly longer than the fetch period to ensure we don't miss measurements due to jitter + }, + ) + + if err != nil { + return nil, fmt.Errorf("GetOrganizationDevicesUplinksLossAndLatency failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + var uplinks []*Uplink + + for _, device := range *val { + uplink := &Uplink{ + DeviceSerial: Serial(device.Serial), + IP: device.IP, + Interface: device.Uplink, + NetworkID: device.NetworkID, + } + + for _, measurement := range *device.TimeSeries { + if measurement.LossPercent != nil || measurement.LatencyMs != nil { + timestamp, err := time.Parse(time.RFC3339, measurement.Ts) + if err != nil { + return nil, fmt.Errorf("failed to parse timestamp [%s] in ResponseOrganizationsGetOrganizationDevicesUplinksLossAndLatency: %w", measurement.Ts, err) + } + + metric := UplinkMetric{Timestamp: timestamp} + if measurement.LossPercent != nil { + metric.LossPercent = measurement.LossPercent + } + if measurement.LatencyMs != nil { + metric.LatencyMs = measurement.LatencyMs + } + uplink.Metrics = append(uplink.Metrics, &metric) + } + } + + if len(uplink.Metrics) != 0 { + uplinks = append(uplinks, uplink) + } + } + + return uplinks, nil +} + +func getDeviceUplinkMetrics(client *meraki_api.Client, organizationID string, period time.Duration) ([]*Uplink, error) { + val, res, err := client.Organizations.GetOrganizationDevicesUplinksLossAndLatency( + organizationID, + &meraki_api.GetOrganizationDevicesUplinksLossAndLatencyQueryParams{ + Timespan: period.Seconds() + 10, // slightly longer than the fetch period to ensure we don't miss measurements due to jitter + }, + ) + + if err != nil { + return nil, fmt.Errorf("GetOrganizationDevicesUplinksLossAndLatency failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + var uplinks []*Uplink + + for _, device := range *val { + uplink := &Uplink{ + DeviceSerial: Serial(device.Serial), + IP: device.IP, + Interface: device.Uplink, + } + + for _, measurement := range *device.TimeSeries { + if measurement.LossPercent != nil || measurement.LatencyMs != nil { + timestamp, err := time.Parse(time.RFC3339, measurement.Ts) + if err != nil { + return nil, fmt.Errorf("failed to parse timestamp [%s] in ResponseOrganizationsGetOrganizationDevicesUplinksLossAndLatency: %w", measurement.Ts, err) + } + + metric := UplinkMetric{Timestamp: timestamp} + if measurement.LossPercent != nil { + metric.LossPercent = measurement.LossPercent + } + if measurement.LatencyMs != nil { + metric.LatencyMs = measurement.LatencyMs + } + uplink.Metrics = append(uplink.Metrics, &metric) + } + } + + if len(uplink.Metrics) != 0 { + uplinks = append(uplinks, uplink) + } + } + + return uplinks, nil +} + +func reportDeviceUplinkMetrics(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, uplinks []*Uplink) { + metrics := []mapstr.M{} + + for _, uplink := range uplinks { + if device, ok := devices[uplink.DeviceSerial]; ok { + metric := mapstr.M{ + "uplink.ip": uplink.IP, + "uplink.interface": uplink.Interface, + // fixme: repeated code serializing device metadata to mapstr + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, + } + + for k, v := range device.Details { + metric[fmt.Sprintf("device.details.%s", k)] = v + } + + for _, uplinkMetric := range uplink.Metrics { + metrics = append(metrics, mapstr.Union(metric, mapstr.M{ + "@timestamp": uplinkMetric.Timestamp, + "uplink.loss_percent": uplinkMetric.LossPercent, + "uplink.latency_ms": uplinkMetric.LatencyMs, + })) + } + } else { + // missing device metadata; ignore + } + } + ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_wireless_device_channel_utilization.go b/x-pack/metricbeat/module/meraki/device_health/device_wireless_device_channel_utilization.go new file mode 100644 index 00000000000..51d7c5cfafd --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_wireless_device_channel_utilization.go @@ -0,0 +1,48 @@ +package device_health + +import ( + "fmt" + + "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func reportWirelessDeviceChannelUtilization(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, wirelessDevices *meraki_api.ResponseOrganizationsGetOrganizationWirelessDevicesChannelUtilizationByDevice) { + + metrics := []mapstr.M{} + + for _, wirelessDevice := range *wirelessDevices { + + if device, ok := devices[Serial(wirelessDevice.Serial)]; ok { + + metric := mapstr.M{ + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, + } + + for _, v := range *wirelessDevice.ByBand { + metric[fmt.Sprintf("wireless.device.channel.utilization.band_%s.wifi.percentage", common.DeDot(v.Band))] = v.Wifi.Percentage + metric[fmt.Sprintf("wireless.device.channel.utilization.band_%s.nonwifi.percentage", common.DeDot(v.Band))] = v.NonWifi.Percentage + metric[fmt.Sprintf("wireless.device.channel.utilization.band_%s.total.percentage", common.DeDot(v.Band))] = v.Total.Percentage + } + + metrics = append(metrics, metric) + + } + + } + ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/device_health/license_overview.go b/x-pack/metricbeat/module/meraki/device_health/license_overview.go new file mode 100644 index 00000000000..6a17bb9bc85 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/license_overview.go @@ -0,0 +1,164 @@ +package device_health + +import ( + "fmt" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func getLicenseStates(client *meraki_api.Client, organizationID string) ([]*CoterminationLicense, []*PerDeviceLicense, *SystemsManagerLicense, error) { + val, res, err := client.Organizations.GetOrganizationLicensesOverview(organizationID) + + if err != nil { + return nil, nil, nil, fmt.Errorf("GetOrganizationLicensesOverview failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + var cotermLicenses []*CoterminationLicense + var perDeviceLicenses []*PerDeviceLicense + var systemsManagerLicense *SystemsManagerLicense + + // co-termination license metrics (all devices share a single expiration date and status) are reported as counts of licenses per-device + if val.LicensedDeviceCounts != nil { + // i don't know why this isn't typed in the SDK - slightly worrying + for device, count := range (*val.LicensedDeviceCounts).(map[string]interface{}) { + cotermLicenses = append(cotermLicenses, &CoterminationLicense{ + DeviceModel: device, + Count: count, + ExpirationDate: val.ExpirationDate, + Status: val.Status, + }) + } + } + + // per-device license metrics (each device has its own expiration date and status) are reported counts of licenses per-state + if val.States != nil { + if val.States.Active != nil { + perDeviceLicenses = append(perDeviceLicenses, &PerDeviceLicense{ + State: "Active", + Count: val.States.Active.Count, + }) + } + + if val.States.Expired != nil { + perDeviceLicenses = append(perDeviceLicenses, &PerDeviceLicense{ + State: "Expired", + Count: val.States.Expired.Count, + }) + } + + if val.States.RecentlyQueued != nil { + perDeviceLicenses = append(perDeviceLicenses, &PerDeviceLicense{ + State: "RecentlyQueued", + Count: val.States.RecentlyQueued.Count, + }) + } + + if val.States.Expiring != nil { + if val.States.Expiring.Critical != nil { + perDeviceLicenses = append(perDeviceLicenses, &PerDeviceLicense{ + State: "Expiring", + ExpirationState: "Critical", + Count: val.States.Expiring.Critical.ExpiringCount, + ExpirationThresholdDays: val.States.Expiring.Critical.ThresholdInDays, + }) + } + + if val.States.Expiring.Warning != nil { + perDeviceLicenses = append(perDeviceLicenses, &PerDeviceLicense{ + State: "Expiring", + ExpirationState: "Warning", + Count: val.States.Expiring.Warning.ExpiringCount, + ExpirationThresholdDays: val.States.Expiring.Warning.ThresholdInDays, + }) + } + } + + if val.States.Unused != nil { + perDeviceLicenses = append(perDeviceLicenses, &PerDeviceLicense{ + State: "Unused", + Count: val.States.Unused.Count, + SoonestActivationDate: val.States.Unused.SoonestActivation.ActivationDate, + SoonestActivationCount: val.States.Unused.SoonestActivation.ToActivateCount, + }) + } + + if val.States.UnusedActive != nil { + perDeviceLicenses = append(perDeviceLicenses, &PerDeviceLicense{ + State: "UnusedActive", + Count: val.States.UnusedActive.Count, + OldestActivationDate: val.States.UnusedActive.OldestActivation.ActivationDate, + OldestActivationCount: val.States.UnusedActive.OldestActivation.ActiveCount, + }) + } + } + + if val.LicenseTypes != nil { + for _, t := range *val.LicenseTypes { + perDeviceLicenses = append(perDeviceLicenses, &PerDeviceLicense{ + State: "Unassigned", + Type: t.LicenseType, + Count: t.Counts.Unassigned, + }) + } + } + + // per-device metrics also contain systems manager metrics + if val.SystemsManager != nil { + systemsManagerLicense = &SystemsManagerLicense{ + TotalSeats: val.SystemsManager.Counts.TotalSeats, + ActiveSeats: val.SystemsManager.Counts.ActiveSeats, + UnassignedSeats: val.SystemsManager.Counts.UnassignedSeats, + OrgwideEnrolledDevices: val.SystemsManager.Counts.OrgwideEnrolledDevices, + } + } + + return cotermLicenses, perDeviceLicenses, systemsManagerLicense, nil +} + +func reportLicenseMetrics(reporter mb.ReporterV2, organizationID string, cotermLicenses []*CoterminationLicense, perDeviceLicenses []*PerDeviceLicense, systemsManagerLicense *SystemsManagerLicense) { + if len(cotermLicenses) != 0 { + cotermLicenseMetrics := []mapstr.M{} + for _, license := range cotermLicenses { + cotermLicenseMetrics = append(cotermLicenseMetrics, mapstr.M{ + "license.device_model": license.DeviceModel, + "license.expiration_date": license.ExpirationDate, + "license.status": license.Status, + "license.count": license.Count, + }) + } + ReportMetricsForOrganization(reporter, organizationID, cotermLicenseMetrics) + } + + if len(perDeviceLicenses) != 0 { + perDeviceLicenseMetrics := []mapstr.M{} + for _, license := range perDeviceLicenses { + perDeviceLicenseMetrics = append(perDeviceLicenseMetrics, mapstr.M{ + "license.state": license.State, + "license.count": license.Count, + "license.expiration_state": license.ExpirationState, + "license.expiration_threshold_days": license.ExpirationThresholdDays, + "license.soonest_activation_date": license.SoonestActivationDate, + "license.soonest_activation_count": license.SoonestActivationCount, + "license.oldest_activation_date": license.OldestActivationDate, + "license.oldest_activation_count": license.OldestActivationCount, + "license.type": license.Type, + }) + } + ReportMetricsForOrganization(reporter, organizationID, perDeviceLicenseMetrics) + } + + if systemsManagerLicense != nil { + + ReportMetricsForOrganization(reporter, organizationID, []mapstr.M{ + { + "license.systems_manager.active_seats": systemsManagerLicense.ActiveSeats, + "license.systems_manager.org_wide_enrolled_devices": systemsManagerLicense.OrgwideEnrolledDevices, + "license.systems_manager.total_seats": systemsManagerLicense.TotalSeats, + "license.systems_manager.unassigned_seats": systemsManagerLicense.UnassignedSeats, + }, + }) + } +} diff --git a/x-pack/metricbeat/module/meraki/device_health/types.go b/x-pack/metricbeat/module/meraki/device_health/types.go new file mode 100644 index 00000000000..8f37963abd2 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/types.go @@ -0,0 +1,98 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package device_health + +import ( + "time" +) + +// device unique identifier +type Serial string + +// network unique identifier +type NetworkID string + +// Device contains static device attributes (i.e. dimensions) +type Device struct { + Address string + Details map[string]string + Firmware string + Imei *float64 + LanIP string + Location []*float64 + Mac string + Model string + Name string + NetworkID string + Notes string + ProductType string // one of ["appliance", "camera", "cellularGateway", "secureConnect", "sensor", "switch", "systemsManager", "wireless", "wirelessController"] + Serial string + Tags []string +} + +// DeviceStatus contains dynamic device attributes +type DeviceStatus struct { + Gateway string + IPType string + LastReportedAt string + PrimaryDNS string + PublicIP string + SecondaryDNS string + Status string // one of ["online", "alerting", "offline", "dormant"] +} + +// Uplink contains static device uplink attributes; uplinks are always associated with a device +type Uplink struct { + DeviceSerial Serial + IP string + Interface string + NetworkID string + Metrics []*UplinkMetric +} + +// UplinkMetric contains timestamped device uplink metric data points +type UplinkMetric struct { + Timestamp time.Time + LossPercent *float64 + LatencyMs *float64 +} + +// CoterminationLicense have a common expiration date and are reported per device model +type CoterminationLicense struct { + ExpirationDate string + DeviceModel string + Status string + Count interface{} +} + +// PerDeviceLicense are reported by license state with details on expiration and activations +type PerDeviceLicense struct { + State string // one of ["Active", "Expired", "RecentlyQueued", "Expiring", "Unused", "UnusedActive", "Unassigned"] + Count *int + ExpirationState string // one of ["critial", "warning"] (only for Expiring licenses) + ExpirationThresholdDays *int // only for Expiring licenses + SoonestActivationDate string // only for Unused licenses + SoonestActivationCount *int // only for Unused licenses + OldestActivationDate string // only for UnusedActive licenses + OldestActivationCount *int // only for UnusedActive licenses + Type string // one of ["ENT", "UPGR", "ADV"] (only for Unassigned licenses) +} + +// SystemManagerLicense reports counts for seats and devices +type SystemsManagerLicense struct { + ActiveSeats *int + OrgwideEnrolledDevices *int + TotalSeats *int + UnassignedSeats *int +} + +type PerfScore struct { + PerformanceScore float64 `json:"perfScore"` +} + +// DeviceStatus contains dynamic device attributes +type DevicePerformanceScore struct { + PerformanceScore float64 +} diff --git a/x-pack/metricbeat/module/meraki/doc.go b/x-pack/metricbeat/module/meraki/doc.go new file mode 100644 index 00000000000..3a33bfa283c --- /dev/null +++ b/x-pack/metricbeat/module/meraki/doc.go @@ -0,0 +1,2 @@ +// Package meraki is a Metricbeat module that contains MetricSets. +package meraki diff --git a/x-pack/metricbeat/module/meraki/fields.go b/x-pack/metricbeat/module/meraki/fields.go new file mode 100644 index 00000000000..37bfb38f6db --- /dev/null +++ b/x-pack/metricbeat/module/meraki/fields.go @@ -0,0 +1,23 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// Code generated by beats/dev-tools/cmd/asset/asset.go - DO NOT EDIT. + +package meraki + +import ( + "github.com/elastic/beats/v7/libbeat/asset" +) + +func init() { + if err := asset.SetFields("metricbeat", "meraki", asset.ModuleFieldsPri, AssetMeraki); err != nil { + panic(err) + } +} + +// AssetMeraki returns asset data. +// This is the base64 encoded zlib format compressed contents of module/meraki. +func AssetMeraki() string { + return "eJx8j0GugyAYhPecYuLeC7B4u3eOhsq0EkEIYltv3yjaUEP6LZnwzfwtBi4SjlENRgDJJEuJJj80Aoi0VBMlrkxKAJpTF01Ixo8SfwLA/hvO69lSADdDqye5ZS1G5Vg0rKQlUOIe/Rz2l4r121O6NB+m46Wnsqn/pDXtyvmAg2rlEdULzoPKUXwpF7bzS/KkgcvTR33KfgxY+c/CXCreAQAA///oWnJl" +} diff --git a/x-pack/metricbeat/modules.d/meraki.yml.disabled b/x-pack/metricbeat/modules.d/meraki.yml.disabled new file mode 100644 index 00000000000..3b45c487b97 --- /dev/null +++ b/x-pack/metricbeat/modules.d/meraki.yml.disabled @@ -0,0 +1,11 @@ +# Module: meraki +# Docs: https://www.elastic.co/guide/en/beats/metricbeat/main/metricbeat-module-meraki.html + +- module: meraki + metricsets: ["device_health"] + enabled: true + period: 60s + # The Meraki API key for your organization. + apiKey: + # A list of organization id's to harvest meraki data. + organizations: [""]