Skip to content

Commit d4dacc7

Browse files
Merge pull request #10 from appbaseio/feat/arc_billing
feat: add billing
2 parents d128c5e + b8ef0ae commit d4dacc7

6 files changed

Lines changed: 185 additions & 4 deletions

File tree

Dockerfile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
FROM golang:1.11-alpine as builder
22

3+
# Default value
4+
# Run `--build-arg BILLING=true` to enable billing
5+
ARG BILLING=false
6+
ENV BILLING="${BILLING}"
7+
38
# Install tools required for project
49
# Run `docker build --no-cache .` to update dependencies
510
RUN apk add --no-cache build-base git
@@ -9,7 +14,7 @@ WORKDIR /arc
914
COPY go.mod go.sum ./
1015

1116
# Install library dependencies
12-
RUN go mod download
17+
RUN go mod download
1318

1419
# Copy the entire project and build it
1520
# This layer is rebuilt when a file changes in the project directory

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ PLUGIN_MAIN_LOC_FUNC=plugins/$(1)/main/$(1).$(2)
99
PLUGIN_LOC_FUNC=$(foreach PLUGIN,$(PLUGINS),$(call PLUGIN_MAIN_LOC_FUNC,$(PLUGIN),$(1)))
1010

1111
cmd: plugins
12-
$(GC) -o $(BUILD_DIR)/arc main.go
12+
$(GC) -ldflags "-X main.Billing=$(BILLING)" -o $(BUILD_DIR)/arc main.go
1313

1414
plugins: $(call PLUGIN_LOC_FUNC,so)
1515

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/gorilla/mux v1.7.1
99
github.com/olivere/elastic v6.2.21+incompatible
1010
github.com/olivere/elastic/v7 v7.0.4
11+
github.com/robfig/cron v1.1.0
1112
github.com/rogpeppe/go-internal v1.2.2 // indirect
1213
github.com/rs/cors v1.6.0
1314
github.com/siddharthlatest/mustache v0.0.0-20160118163553-00029677272d

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
22
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3+
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
34
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
45
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
56
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
@@ -32,6 +33,7 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
3233
github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
3334
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
3435
github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
36+
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
3537
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
3638
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
3739
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@@ -220,6 +222,7 @@ github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
220222
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
221223
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
222224
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
225+
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
223226
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
224227
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
225228
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -317,6 +320,8 @@ github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
317320
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
318321
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
319322
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
323+
github.com/robfig/cron v1.1.0 h1:jk4/Hud3TTdcrJgUOBgsqrZBarcxl6ADIjSC2iniwLY=
324+
github.com/robfig/cron v1.1.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
320325
github.com/rogpeppe/go-internal v1.0.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
321326
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
322327
github.com/rogpeppe/go-internal v1.2.2 h1:J7U/N7eRtzjhs26d6GqMh2HBuXP8/Z64Densiiieafo=
@@ -483,6 +488,7 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXL
483488
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
484489
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
485490
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
491+
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
486492
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
487493
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
488494
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

main.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ import (
1616
"github.com/appbaseio/arc/middleware"
1717
"github.com/appbaseio/arc/middleware/logger"
1818
"github.com/appbaseio/arc/plugins"
19+
"github.com/appbaseio/arc/util"
1920
"github.com/gorilla/mux"
21+
"github.com/robfig/cron"
2022
"github.com/rs/cors"
2123

2224
"gopkg.in/natefinch/lumberjack.v2"
@@ -31,7 +33,8 @@ var (
3133
address string
3234
port int
3335
pluginDir string
34-
https bool
36+
https bool
37+
Billing string
3538
)
3639

3740
func init() {
@@ -71,8 +74,19 @@ func main() {
7174

7275
router := mux.NewRouter().StrictSlash(true)
7376

77+
if Billing == "true" {
78+
log.Println("You're running Arc with billing module enabled.")
79+
util.ReportUsage()
80+
cronjob := cron.New()
81+
cronjob.AddFunc("@every 1h", util.ReportUsage)
82+
cronjob.Start()
83+
router.Use(util.BillingMiddleware)
84+
} else {
85+
log.Println("You're running Arc with billing module disabled.")
86+
}
87+
7488
var elasticSearchPath string
75-
elasticSearchMiddleware := make([] middleware.Middleware, 0)
89+
elasticSearchMiddleware := make([]middleware.Middleware, 0)
7690
err := filepath.Walk(pluginDir, func(path string, info os.FileInfo, err error) error {
7791
if err != nil {
7892
return err

util/billing.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package util
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"io/ioutil"
8+
"log"
9+
"net/http"
10+
"os"
11+
"time"
12+
13+
"github.com/olivere/elastic/v7"
14+
)
15+
16+
var TimeValidity int64
17+
var MAX_ALLOWED_TIME int64 = 24 // in hrs
18+
19+
type ArcUsage struct {
20+
ArcID string `json:"arc_id"`
21+
Timestamp int64 `json:"timestamp"`
22+
SubscriptionID string `json:"subscription_id"`
23+
Quantity int `json:"quantity"`
24+
Email string `json:"email"`
25+
}
26+
27+
type ArcUsageResponse struct {
28+
Accepted bool `json:"accepted"`
29+
FailureReason string `json:"failure_reason"`
30+
ErrorMsg string `json:"error_msg"`
31+
WarningMsg string `json:"warning_msg"`
32+
StatusCode int `json:"status_code"`
33+
TimeValidity int64 `json:"time_validity"`
34+
}
35+
36+
const (
37+
envEsURL = "ES_CLUSTER_URL"
38+
arcIdentifier = "ARC_ID"
39+
emailID = "EMAIL"
40+
subscriptionID = "SUBSCRIPTION_ID"
41+
)
42+
43+
// Middleware function, which will be called for each request
44+
func BillingMiddleware(next http.Handler) http.Handler {
45+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
46+
if TimeValidity > 0 { // Valid plan
47+
next.ServeHTTP(w, r)
48+
} else if -(TimeValidity) <= 3600*MAX_ALLOWED_TIME {
49+
// Print warning message if remaining time is less than max allowed time
50+
if TimeValidity == 0 { // Rare, but it can happen when tier has been just expired
51+
log.Println("warning: payment required. arc will start sending out error messages in some time")
52+
} else {
53+
log.Println("warning: payment required. arc will start sending out error messages in next", TimeValidity/3600, "hours")
54+
}
55+
next.ServeHTTP(w, r)
56+
} else {
57+
// Write an error and stop the handler chain
58+
http.Error(w, "payment required", http.StatusPaymentRequired)
59+
}
60+
})
61+
}
62+
63+
func ReportUsageRequest(arcUsage ArcUsage) (ArcUsageResponse, error) {
64+
response := ArcUsageResponse{}
65+
url := "https://accapi.appbase.io/arc/" + arcUsage.ArcID + "/report_usage"
66+
marshalledRequest, err := json.Marshal(arcUsage)
67+
if err != nil {
68+
log.Println("error while marshalling req body: ", err)
69+
return response, err
70+
}
71+
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(marshalledRequest))
72+
req.Header.Add("Content-Type", "application/json")
73+
req.Header.Add("cache-control", "no-cache")
74+
75+
res, err := http.DefaultClient.Do(req)
76+
if err != nil {
77+
log.Println("error while sending request: ", err)
78+
return response, err
79+
}
80+
defer res.Body.Close()
81+
body, err := ioutil.ReadAll(res.Body)
82+
if err != nil {
83+
log.Println("error reading res body: ", err)
84+
return response, err
85+
}
86+
err = json.Unmarshal(body, &response)
87+
88+
if err != nil {
89+
log.Println("error while unmarshalling res body: ", err)
90+
return response, err
91+
}
92+
return response, nil
93+
}
94+
95+
func ReportUsage() {
96+
url := os.Getenv(envEsURL)
97+
if url == "" {
98+
log.Fatalln("ES_CLUSTER_URL not found")
99+
}
100+
arcID := os.Getenv(arcIdentifier)
101+
if url == "" {
102+
log.Fatalln("ARC_ID not found")
103+
}
104+
email := os.Getenv(emailID)
105+
if url == "" {
106+
log.Fatalln("EMAIL not found")
107+
}
108+
subID := os.Getenv(subscriptionID)
109+
if url == "" {
110+
log.Println("SUBSCRIPTION_ID not found. Initializing in trial mode")
111+
}
112+
nodeCount, err := FetchNodeCount(url)
113+
if err != nil || nodeCount == -1 {
114+
log.Println("unable to fetch node count: ", err)
115+
}
116+
usageBody := ArcUsage{}
117+
usageBody.ArcID = arcID
118+
usageBody.Email = email
119+
usageBody.SubscriptionID = subID
120+
usageBody.Timestamp = time.Now().Unix()
121+
usageBody.Quantity = nodeCount
122+
response, err := ReportUsageRequest(usageBody)
123+
if err != nil {
124+
log.Println("please contact support. Usage not getting reported: ", err)
125+
}
126+
127+
TimeValidity = response.TimeValidity
128+
if response.WarningMsg != "" {
129+
log.Println("warning:", response.WarningMsg)
130+
}
131+
if response.ErrorMsg != "" {
132+
log.Println("error:", response.ErrorMsg)
133+
}
134+
}
135+
136+
func FetchNodeCount(url string) (int, error) {
137+
ctx := context.Background()
138+
// Initialize the client
139+
client, err := elastic.NewClient(
140+
elastic.SetURL(url),
141+
elastic.SetRetrier(NewRetrier()),
142+
elastic.SetSniff(false),
143+
elastic.SetHttpClient(HTTPClient()),
144+
)
145+
if err != nil {
146+
log.Fatalln("unable to initialize elastic client: ", err)
147+
}
148+
nodes, err := client.NodesInfo().
149+
Metric("nodes").
150+
Do(ctx)
151+
if err != nil {
152+
return -1, err
153+
}
154+
return len(nodes.Nodes), nil
155+
}

0 commit comments

Comments
 (0)