/
client.go
120 lines (102 loc) · 3.14 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package mlp
import (
"context"
"fmt"
"log"
"net/http"
"time"
mlp "github.com/gojek/mlp/api/client"
cache "github.com/patrickmn/go-cache"
"golang.org/x/oauth2/google"
"github.com/caraml-dev/timber/common/errors"
)
const (
mlpCacheExpirySeconds = 600
mlpCacheCleanUpSeconds = 900
mlpQueryTimeoutSeconds = 30
// GoogleOAuthScope is the default Google OAuth Scope
GoogleOAuthScope = "https://www.googleapis.com/auth/userinfo.email"
)
// Client provides a set of methods to interact with the MLP APIs
type Client interface {
// GetProject gets the project matching the provided id
GetProject(id int64) (*mlp.Project, error)
}
type client struct {
mlpClient *mlpClient
cache *cache.Cache
}
type mlpClient struct {
api *mlp.APIClient
}
func newMLPClient(googleClient *http.Client, basePath string) *mlpClient {
cfg := mlp.NewConfiguration()
cfg.BasePath = basePath
cfg.HTTPClient = googleClient
return &mlpClient{mlp.NewAPIClient(cfg)}
}
// NewClient returns a service that retrieves information that is shared across MLP projects.
func NewClient(mlpBasePath string) (Client, error) {
httpClient := http.DefaultClient
googleClient, err := google.DefaultClient(context.Background(), GoogleOAuthScope)
if err == nil {
httpClient = googleClient
} else {
log.Println("Google default credential not found. Fallback to HTTP default client")
}
svc := &client{
mlpClient: newMLPClient(httpClient, mlpBasePath),
cache: cache.New(mlpCacheExpirySeconds*time.Second, mlpCacheCleanUpSeconds*time.Second),
}
err = svc.refreshProjects()
if err != nil {
return nil, err
}
return svc, nil
}
// GetProject gets the project matching the provided id. This method will hit the cache first,
// and if not found, will call MLP API once to get the updated list of projects and refresh the cache,
// then try to get the value again. If still not found, will return a freecache NotFound error.
func (service *client) GetProject(id int64) (*mlp.Project, error) {
project, err := service.getProject(id)
if err != nil {
err = service.refreshProjects()
if err != nil {
return nil, err
}
return service.getProject(id)
}
return project, nil
}
func (service *client) getProject(id int64) (*mlp.Project, error) {
key := buildProjectKey(id)
cachedValue, found := service.cache.Get(key)
if !found {
return nil, errors.Newf(errors.NotFound, "MLP Project info for id %d not found in the cache", id)
}
// Cast the data
project, ok := cachedValue.(mlp.Project)
if !ok {
return nil, errors.Newf(errors.NotFound, "Malformed project info found in the cache for id %d", id)
}
return &project, nil
}
func (service *client) refreshProjects() error {
ctx, cancel := context.WithTimeout(context.Background(), mlpQueryTimeoutSeconds*time.Second)
defer cancel()
projects, resp, err := service.mlpClient.api.ProjectApi.ProjectsGet(ctx, nil)
if err != nil {
return err
}
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}
for _, project := range projects {
key := buildProjectKey(int64(project.ID))
service.cache.Set(key, project, cache.DefaultExpiration)
}
return nil
}
func buildProjectKey(id int64) string {
return fmt.Sprintf("proj:%d", id)
}