-
Notifications
You must be signed in to change notification settings - Fork 77
/
rtcservice.go
185 lines (171 loc) · 7.03 KB
/
rtcservice.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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// Copyright 2017 The Cloudprober Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package rtcservice provides utility functions for the cloudprober project
// used when dealing with the Runtime Configurator API.
// https://cloud.google.com/deployment-manager/runtime-configurator/reference/rest/
// This API essentially provides a small key/val store for GCE
// projects. Cloudprober uses this for things like maintaining a lameduck list,
// listing probing hosts in the projects, and in general any shared state that
// cloudprober instances might require.
//
// Because RTC requires a GCE project, this package provides all functionality
// as methods on the Config interface. This allows the behavior to be more
// easily mocked for testing.
//
// rtcservice.Config is meant to represent a configuration or resource in the
// RTC sense (see https://cloud.google.com/deployment-manager/runtime-configurator/).
// If one needs to interact with multiple configurations, they will need multiple
// instances of rtc.Config.
package rtcservice
import (
"context"
"encoding/base64"
"fmt"
"net/http"
"golang.org/x/oauth2/google"
"google.golang.org/api/googleapi"
runtimeconfig "google.golang.org/api/runtimeconfig/v1beta1"
)
// The Config interface provides communication to the Runtime Configurator API.
// It represents a single runtime configuration, allowing one to read and write
// variables to the configuration.
type Config interface {
// GetProject returns the project name for the configuration
GetProject() string
// Write adds or changes a key value pair to a configuration.
Write(key string, val []byte) error
// Delete removes a variable from a configuration.
Delete(key string) error
// Val returns the Value stored by a variable.
Val(v *runtimeconfig.Variable) ([]byte, error)
// List lists all variables in a configuration.
List() ([]*runtimeconfig.Variable, error)
// FilterList will list all variables in a configuration, filtering variable
// names by the filter string. This mirrors the behavior found in
// https://cloud.google.com/deployment-manager/runtime-configurator/reference/rest/v1beta1/projects.configs.variables/list
FilterList(filter string) ([]*runtimeconfig.Variable, error)
}
// Full-featured implementation of Rtc interface. Only accessible through the
// New function.
type impl struct {
svc *runtimeconfig.ProjectsConfigsVariablesService
proj string
cfg string
}
// New provides an interface to the RTC API for a given project. The string proj
// represents the project name (such as "google.com:bbmc-test"), and the string
// "cfg" will represent the name of the RTC resource.
//
// In order to provide fail-fast sanitation, New will check that the provided
// project string and cfg string are reachable. If not, an error will be returned.
// Note that this means New cannot be used to establish a new RTC configuration ---
// the configuration must already exist.
//
// New also takes an OAuth2.0 enabled *http.Client for API access. If a nil
// *http.Client is provided, a new http.Client is created using
// google.DefaultClient, which uses default credentials.
func New(proj string, cfg string, c *http.Client) (Config, error) {
svc, err := getService(c)
if err != nil {
return nil, err
}
// TODO: Consider checking for the configuration errors before returning.
return &impl{svc, proj, cfg}, nil
}
// This helper function is used to actually connect to an RTC client.
func getService(c *http.Client) (*runtimeconfig.ProjectsConfigsVariablesService, error) {
if c == nil {
var err error
c, err = google.DefaultClient(context.TODO(), runtimeconfig.CloudruntimeconfigScope)
if err != nil {
return nil, err
}
}
rtcService, err := runtimeconfig.New(c)
if err != nil {
return nil, err
}
return runtimeconfig.NewProjectsConfigsVariablesService(rtcService), nil
}
// GetProject will return the alphanumeric project ID for the configuration
func (s *impl) GetProject() string {
return s.proj
}
// Write will add or change key/val pair in the configuration for s's project
// using the RTC API. An empty key will return an error, however empty vals are
// perfectly fine, and will return a variable with an empty-string value.
func (s *impl) Write(key string, val []byte) error {
path := "projects/" + s.proj + "/configs/" + s.cfg
encoded := base64.StdEncoding.EncodeToString(val)
v := runtimeconfig.Variable{
Name: path + "/variables/" + key,
Value: encoded,
// ForceSendFields will force value to be sent, even if empty.
ForceSendFields: []string{"value"},
}
// Create. If error, check error type, and maybe update.
_, err := s.svc.Create(path, &v).Do()
if err != nil {
// If error is 'ALREADY_EXISTS', then we call update.
apierr, ok := err.(*googleapi.Error)
if ok && apierr.Message == "Requested entity already exists" {
path += "/variables/" + key
v.Name = ""
_, err = s.svc.Update(path, &v).Do()
if err != nil {
return fmt.Errorf("rtc.Write %#v : unable to update variable : %v", v, err)
}
} else {
return fmt.Errorf("rtc.Write %#v : unable to create variable : %v", v, err)
}
}
return err
}
// Delete removes a key/val pair from the configuration in s's project using
// the RTC API. An empty key will return an error.
func (s *impl) Delete(key string) error {
path := "projects/" + s.proj + "/configs/" + s.cfg + "/variables/" + key
_, err := s.svc.Delete(path).Do()
return err
}
// Val attempts to decode the value for a given Variable.
func (s *impl) Val(v *runtimeconfig.Variable) ([]byte, error) {
decoded, err := base64.StdEncoding.DecodeString(v.Value)
if err != nil {
return nil, fmt.Errorf("rtc.Val %#v : unable to decode value : %v", v, err)
}
return decoded, nil
}
// List provides a slice of all variables in the configuration for s's
// project using the RTC API.
func (s *impl) List() ([]*runtimeconfig.Variable, error) {
path := "projects/" + s.proj + "/configs/" + s.cfg
resp, err := s.svc.List(path).ReturnValues(true).Do()
if err != nil {
return nil, err
}
return resp.Variables, nil
}
// FilterList provides a slice of all variables in the configuration for s's
// project using the RTC API, filtered by the given filter string. More about this
// behavior can be found in the RTC documentation.
// https://cloud.google.com/deployment-manager/runtime-configurator/reference/rest/v1beta1/projects.configs.variables/list
func (s *impl) FilterList(filter string) ([]*runtimeconfig.Variable, error) {
path := "projects/" + s.proj + "/configs/" + s.cfg
resp, err := s.svc.List(path).Filter(filter).ReturnValues(true).Do()
if err != nil {
return nil, err
}
return resp.Variables, nil
}