Skip to content

Commit

Permalink
Merge pull request #501 from innogames/md_ripe_atlas
Browse files Browse the repository at this point in the history
Md ripe atlas
  • Loading branch information
brainexe authored Nov 20, 2023
2 parents a1cc236 + 86217e8 commit 9add643
Show file tree
Hide file tree
Showing 7 changed files with 726 additions and 0 deletions.
4 changes: 4 additions & 0 deletions command/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/innogames/slack-bot/v2/command/pool"
"github.com/innogames/slack-bot/v2/command/pullrequest"
"github.com/innogames/slack-bot/v2/command/queue"
"github.com/innogames/slack-bot/v2/command/ripeatlas"
"github.com/innogames/slack-bot/v2/command/vcs"
"github.com/innogames/slack-bot/v2/command/weather"
)
Expand Down Expand Up @@ -77,6 +78,9 @@ func GetCommands(slackClient client.SlackClient, cfg config.Config) *bot.Command
// vcs branch watcher
commands.Merge(vcs.GetCommands(base, &cfg))

// Ripe Atlas
commands.Merge(ripeatlas.GetCommands(base, &cfg))

return commands
}

Expand Down
182 changes: 182 additions & 0 deletions command/ripeatlas/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package ripeatlas

import (
"encoding/json"
"fmt"
"time"
)

type CreditsResponse struct {
CurrentBalance int `json:"current_balance"`
CreditChecked bool `json:"credit_checked"`
MaxDailyCredits int `json:"max_daily_credits"`
EstimatedDailyIncome int `json:"estimated_daily_income"`
EstimatedDailyExpenditure int `json:"estimated_daily_expenditure"`
EstimatedDailyBalance int `json:"estimated_daily_balance"`
CalculationTime string `json:"calculation_time"`
EstimatedRunoutSeconds any `json:"estimated_runout_seconds"`
PastDayMeasurementResults int `json:"past_day_measurement_results"`
PastDayCreditsSpent int `json:"past_day_credits_spent"`
LastDateDebited string `json:"last_date_debited"`
LastDateCredited string `json:"last_date_credited"`
IncomeItems string `json:"income_items"`
ExpenseItems string `json:"expense_items"`
Transactions string `json:"transactions"`
}

type MeasurementsResponse struct {
Count int `json:"count,omitempty"`
Next string `json:"next,omitempty"`
Previous string `json:"previous,omitempty"`
Measurements []Measurement `json:"results"`
}

type MeasurementResult struct {
Measurements []int `json:"measurements"`
}

type Measurement struct {
AddressFamily int `json:"af"`
CreationTime int `json:"creation_time"`
CreditsPerResult int `json:"credits_per_result"`
Description string `json:"description"`
EstimatedResultsPerDay int `json:"estimated_results_per_day"`
Group string `json:"group"`
GroupID int64 `json:"group_id"`
ID int `json:"id"`
InWifiGroup bool `json:"in_wifi_group"`
IncludeProbeID bool `json:"include_probe_id"`
Interval int `json:"interval"`
IsAllScheduled bool `json:"is_all_scheduled"`
IsOneoff bool `json:"is_oneoff"`
IsPublic bool `json:"is_public"`
PacketInterval int64 `json:"packet_interval"`
Packets int `json:"packets"`
ParticipantCount int64 `json:"participant_count"`
ProbesRequested int64 `json:"probes_requested"`
ProbesScheduled int64 `json:"probes_scheduled"`
ResolveOnProbe bool `json:"resolve_on_probe"`
ResolvedIps string `json:"resolved_ips"`
Result string `json:"result"`
Size int64 `json:"size"`
Spread int64 `json:"spread"`
StartTime int `json:"start_time"`
Status MeasurementStatus `json:"status"`
StopTime int `json:"stop_time"`
Tags []string `json:"tags"`
Target string `json:"target"`
TargetAsn int64 `json:"target_asn"`
TargetIP string `json:"target_ip"`
TargetPrefix string `json:"target_prefix"`
Type string `json:"type"`
}
type MeasurementStatus struct {
Name string `json:"name"`
ID int `json:"id"`
When any `json:"when"`
}

type MeasurementRequest struct {
Definitions []MeasurementDefinition `json:"definitions"`
Probes []Probes `json:"probes"`
IsOneOff bool `json:"is_oneoff"`
}
type MeasurementDefinition struct {
Target string `json:"target,omitempty"`
Af int `json:"af,omitempty"`
ResponseTimeout int `json:"response_timeout,omitempty"`
Description string `json:"description,omitempty"`
Protocol string `json:"protocol,omitempty"`
ResolveOnProbe bool `json:"resolve_on_probe,omitempty"`
Packets int `json:"packets,omitempty"`
Size int `json:"size,omitempty"`
FirstHop int `json:"first_hop,omitempty"`
MaxHops int `json:"max_hops,omitempty"`
Paris int `json:"paris,omitempty"`
DestinationOptionSize int `json:"destination_option_size,omitempty"`
HopByHopOptionSize int `json:"hop_by_hop_option_size,omitempty"`
DontFragment bool `json:"dont_fragment,omitempty"`
SkipDNSCheck bool `json:"skip_dns_check,omitempty"`
Type string `json:"type,omitempty"`
IsPublic bool `json:"is_public"`
}
type Probes struct {
Type string `json:"type,omitempty"`
Value string `json:"value,omitempty"`
Requested int `json:"requested,omitempty"`
}

type StreamingResponse struct {
Type string `json:"type"`
Payload StreamingResponsePayload `json:"payload"`
}

func (sr *StreamingResponse) UnmarshalJSON(b []byte) error {
a := []interface{}{&sr.Type, &sr.Payload}
return json.Unmarshal(b, &a)
}

type StreamingResponsePayload struct {
Fw int `json:"fw,omitempty"`
Mver string `json:"mver,omitempty"`
Lts int `json:"lts,omitempty"`
Endtime int `json:"endtime,omitempty"`
DstName string `json:"dst_name,omitempty"`
DstAddr string `json:"dst_addr,omitempty"`
SrcAddr string `json:"src_addr,omitempty"`
Proto string `json:"proto,omitempty"`
Af int `json:"af,omitempty"`
Size int `json:"size,omitempty"`
ParisID int `json:"paris_id,omitempty"`
Result []PayloadResult `json:"result,omitempty"`
MsmID int `json:"msm_id,omitempty"`
PrbID int `json:"prb_id,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
MsmName string `json:"msm_name,omitempty"`
From string `json:"from,omitempty"`
Type string `json:"type,omitempty"`
GroupID int `json:"group_id,omitempty"`
}

func (srp StreamingResponsePayload) String() string {
// Start: 2023-08-03T14:01:07Z
// HOST: 2a02:1811:c1c:7800:a62b:b0ff:fef1:5062 Loss% Last
// 1 . AS0 172.20.0.1 0% 0.139
// 2 . AS0 172.26.4.1 0% 0.397
// 3 . AS0 192.168.144.1 0% 1.693
var text string
text += "```\n"
text += fmt.Sprintf("Start: %s\n", time.Unix(srp.Timestamp, 0))
text += fmt.Sprintf("HOST: %-40s Loss%% RTT\n", srp.SrcAddr)

for _, res := range srp.Result {
var from string
switch {
case len(res.Result[0].From) > 0:
from = res.Result[0].From
case len(res.Result[1].From) > 0:
from = res.Result[1].From
case len(res.Result[2].From) > 0:
from = res.Result[2].From
default:
from = "???"
}

text += fmt.Sprintf("%2d . %-40s %4d%% %7.3f %7.3f %7.3f\n", res.Hop, from, 0, res.Result[0].Rtt, res.Result[1].Rtt, res.Result[2].Rtt)
}

text += "```\n"
return text
}

type HopResult struct {
From string `json:"from,omitempty"`
TTL int `json:"ttl,omitempty"`
Size int `json:"size,omitempty"`
Rtt float64 `json:"rtt,omitempty"`
Loss string `json:"x,omitempty"`
}
type PayloadResult struct {
Hop int `json:"hop,omitempty"`
Result []HopResult `json:"result,omitempty"`
}
27 changes: 27 additions & 0 deletions command/ripeatlas/commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ripeatlas

import (
"github.com/innogames/slack-bot/v2/bot"
"github.com/innogames/slack-bot/v2/bot/config"
)

var category = bot.Category{
Name: "RIPE Atlas",
Description: "Run queries against the RIPE Atlas API to debug network issues",
}

func GetCommands(base bot.BaseCommand, config *config.Config) bot.Commands {
var commands bot.Commands

cfg := loadConfig(config)
if !cfg.IsEnabled() {
return commands
}

commands.AddCommand(
&creditsCommand{base, cfg},
&tracerouteCommand{base, cfg},
)

return commands
}
33 changes: 33 additions & 0 deletions command/ripeatlas/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package ripeatlas

import (
"time"

"github.com/innogames/slack-bot/v2/bot/config"
)

// Config configuration: API key to do API calls
type Config struct {
APIKey string `mapstructure:"api_key"`
APIURL string `mapstructure:"api_url"`
StreamURL string `mapstructure:"stream_url"`
UpdateInterval time.Duration `mapstructure:"update_interval"`
}

// IsEnabled checks if token is set
func (c *Config) IsEnabled() bool {
return c.APIKey != ""
}

var defaultConfig = Config{
APIURL: "https://atlas.ripe.net/api/v2",
StreamURL: "https://atlas-stream.ripe.net/stream/",
UpdateInterval: time.Second,
}

func loadConfig(config *config.Config) Config {
cfg := defaultConfig
_ = config.LoadCustom("ripeatlas", &cfg)

return cfg
}
76 changes: 76 additions & 0 deletions command/ripeatlas/credits.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package ripeatlas

import (
"encoding/json"
"fmt"
"net/http"

"github.com/innogames/slack-bot/v2/bot"
"github.com/innogames/slack-bot/v2/bot/matcher"
"github.com/innogames/slack-bot/v2/bot/msg"
"github.com/innogames/slack-bot/v2/client"
log "github.com/sirupsen/logrus"
)

type creditsCommand struct {
bot.BaseCommand
cfg Config
}

func (c *creditsCommand) GetMatcher() matcher.Matcher {
return matcher.NewGroupMatcher(
matcher.NewTextMatcher("credits", c.credits),
)
}

func (c *creditsCommand) credits(_ matcher.Result, message msg.Message) {
c.AddReaction(":coffee:", message)
defer c.RemoveReaction(":coffee:", message)

url := fmt.Sprintf("%s/credits", c.cfg.APIURL)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
c.ReplyError(message, fmt.Errorf("request creation returned an err: %w", err))
log.Errorf("request creation returned an err: %s", err)
return
}

req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Key "+c.cfg.APIKey)

response, err := client.GetHTTPClient().Do(req)
if err != nil {
c.ReplyError(message, fmt.Errorf("API call returned an err: %w", err))
log.Errorf("API call returned an err: %s", err)
return
}
defer response.Body.Close()

if response.StatusCode >= 400 {
c.ReplyError(message, fmt.Errorf("API call returned an err: %d", response.StatusCode))
log.Errorf("API call returned an err: %d", response.StatusCode)
return
}

var result CreditsResponse
err = json.NewDecoder(response.Body).Decode(&result)
if err != nil {
c.ReplyError(message, err)
log.Errorf("%s", err)
return
}

text := fmt.Sprintf("Total credits remaining: %d", result.CurrentBalance)

c.SendMessage(message, text)
}

func (c *creditsCommand) GetHelp() []bot.Help {
return []bot.Help{
{
Command: "credits",
Description: "Query how many credits are available for this API key",
Category: category,
},
}
}
Loading

0 comments on commit 9add643

Please sign in to comment.