Skip to content

Commit

Permalink
configuration parser, project structure
Browse files Browse the repository at this point in the history
  • Loading branch information
gweebg committed Jan 26, 2024
1 parent 2c9ee93 commit 080ead0
Show file tree
Hide file tree
Showing 8 changed files with 325 additions and 0 deletions.
48 changes: 48 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
### Go template
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

# Go workspace file
go.work

### Go.AllowList template
# Allowlisting gitignore template for GO projects prevents us
# from adding various unwanted local files, such as generated
# files, developer configurations or IDE-specific files etc.
#
# Recommended: Go.AllowList.gitignore

# Ignore everything
*

# But not these files...
!/.gitignore

!*.go
!go.sum
!go.mod

!README.md
!LICENSE

# !Makefile

# ...even if they are in subdirectories
!*/

26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
### Functional Requirements

1. Fetch IP address from multiple sources (can be chosen by the user);
2. Scripts can be either Python or Bash.
3. App should expose an API for the changes and checks;
4. App should have extensive logging;
5. App should be able to notify users by email;
6. App configuration should be done via a YAML file.

### User Requirements

1. User can chose ways to be notified if a change happens;
2. User can define the delta between checks;
3. User can specify a script to run if the address has, or not changed;
4. User can choose to check IPv6, IPv4 or both ips.

### Configuration

1. url only needs v4 or v6, or both to be specified
2. url must be a valid address
3. response_type is mandatory
4. response_type can be json | text
5. field is mandatory when response_type is json
6. field must be present in the response from the api and represents the ip field
7. default source is always the first, the others are used as fallbac1.
8. timeout is specified in seconds
33 changes: 33 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

import (
"github.com/gweebg/ipwatcher/internal/config"
"log"
)

func main() {

config.Init()

v4 := "v4"
v6 := "v6"
f := "field"

s := config.Source{
Name: "mock",
Url: config.SourceUrl{
V4: &v4,
V6: &v6,
},
Type: "json",
Field: &f,
}
err := config.AddSource(s)

if err != nil {

log.Print(err.Error())
} else {
log.Println("good")
}
}
27 changes: 27 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module github.com/gweebg/ipwatcher

go 1.21.1

require github.com/spf13/viper v1.18.2

require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
52 changes: 52 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
97 changes: 97 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package config

import (
"errors"
"github.com/gweebg/ipwatcher/internal/utils"
"github.com/spf13/viper"
)

var config *viper.Viper

type SourceUrl struct {
V4 *string `mapstructure:"v4"`
V6 *string `mapstructure:"v6"`
}

type Source struct {
Name string `mapstructure:"name"`
Url SourceUrl `mapstructure:"url"`
Type string `mapstructure:"type"`
Field *string `mapstructure:"field"`
}

func Init() {
// Todo: Maybe hot-reload ?
config = viper.New()

config.SetConfigType("yaml")
config.SetConfigName("config")
config.AddConfigPath("./")
config.AddConfigPath("config/")

err := config.ReadInConfig()
utils.Check(err, "")

err = validateSources()
utils.Check(err, "")

}

func GetSources() ([]Source, error) {

var sources []Source

if config == nil {
return nil, errors.New("the 'sources' field can only be acquired after config initialization")
}

err := config.UnmarshalKey("sources", &sources)
if err != nil {
return nil, err
}

return sources, nil
}

func AddSource(newSource Source) error {

sources := config.Get("sources").([]interface{})
sources = append(sources, newSource)

config.Set("sources", sources)
if err := config.WriteConfig(); err != nil {
return err
}

return nil
}

func GetConfig() *viper.Viper {
return config
}

func validateSources() error {

sources, err := GetSources()
if err != nil {
return err
}

for _, source := range sources {

if !(source.Url.V4 != nil || source.Url.V6 != nil) {
return errors.New("the 'url' field must have at 'v4' or 'v6' or both specified")
}

if source.Type == "json" && source.Field == nil {
return errors.New("the 'field' field must be specified if 'response_type' is equal to 'json'")
}

if source.Type != "text" && source.Type != "json" {
return errors.New("the field 'response_type' can only be 'text' or 'json'")
}

}

return nil
}
29 changes: 29 additions & 0 deletions internal/fetch/fetch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package fetch

import (
"bytes"
"encoding/json"
"net/http"
)

func SendHTTPRequest(method, url string, headers map[string]string, payload interface{}) (*http.Response, error) {

jsonPayload, err := json.Marshal(payload)
if err != nil {
return nil, err
}

req, err := http.NewRequest(method, url, bytes.NewBuffer(jsonPayload))
if err != nil {
return nil, err
}

req.Header.Set("Content-Type", "application/json")

for key, value := range headers {
req.Header.Set(key, value)
}

client := &http.Client{}
return client.Do(req)
}
13 changes: 13 additions & 0 deletions internal/utils/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package utils

import "log"

func Check(err error, format string, args ...interface{}) {
if err != nil {
if format != "" {
log.Fatalf(format, args...)
} else {
log.Fatal(err.Error())
}
}
}

0 comments on commit 080ead0

Please sign in to comment.