github.com/google/cloudprober@v0.11.3/targets/rtc/rtcservice/rtcservice.go (about) 1 // Copyright 2017 The Cloudprober Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package rtcservice provides utility functions for the cloudprober project 16 // used when dealing with the Runtime Configurator API. 17 // https://cloud.google.com/deployment-manager/runtime-configurator/reference/rest/ 18 // This API essentially provides a small key/val store for GCE 19 // projects. Cloudprober uses this for things like maintaining a lameduck list, 20 // listing probing hosts in the projects, and in general any shared state that 21 // cloudprober instances might require. 22 // 23 // Because RTC requires a GCE project, this package provides all functionality 24 // as methods on the Config interface. This allows the behavior to be more 25 // easily mocked for testing. 26 // 27 // rtcservice.Config is meant to represent a configuration or resource in the 28 // RTC sense (see https://cloud.google.com/deployment-manager/runtime-configurator/). 29 // If one needs to interact with multiple configurations, they will need multiple 30 // instances of rtc.Config. 31 package rtcservice 32 33 import ( 34 "context" 35 "encoding/base64" 36 "fmt" 37 "net/http" 38 39 "golang.org/x/oauth2/google" 40 "google.golang.org/api/googleapi" 41 runtimeconfig "google.golang.org/api/runtimeconfig/v1beta1" 42 ) 43 44 // The Config interface provides communication to the Runtime Configurator API. 45 // It represents a single runtime configuration, allowing one to read and write 46 // variables to the configuration. 47 type Config interface { 48 // GetProject returns the project name for the configuration 49 GetProject() string 50 // Write adds or changes a key value pair to a configuration. 51 Write(key string, val []byte) error 52 // Delete removes a variable from a configuration. 53 Delete(key string) error 54 // Val returns the Value stored by a variable. 55 Val(v *runtimeconfig.Variable) ([]byte, error) 56 // List lists all variables in a configuration. 57 List() ([]*runtimeconfig.Variable, error) 58 // FilterList will list all variables in a configuration, filtering variable 59 // names by the filter string. This mirrors the behavior found in 60 // https://cloud.google.com/deployment-manager/runtime-configurator/reference/rest/v1beta1/projects.configs.variables/list 61 FilterList(filter string) ([]*runtimeconfig.Variable, error) 62 } 63 64 // Full-featured implementation of Rtc interface. Only accessible through the 65 // New function. 66 type impl struct { 67 svc *runtimeconfig.ProjectsConfigsVariablesService 68 proj string 69 cfg string 70 } 71 72 // New provides an interface to the RTC API for a given project. The string proj 73 // represents the project name (such as "google.com:bbmc-test"), and the string 74 // "cfg" will represent the name of the RTC resource. 75 // 76 // In order to provide fail-fast sanitation, New will check that the provided 77 // project string and cfg string are reachable. If not, an error will be returned. 78 // Note that this means New cannot be used to establish a new RTC configuration --- 79 // the configuration must already exist. 80 // 81 // New also takes an OAuth2.0 enabled *http.Client for API access. If a nil 82 // *http.Client is provided, a new http.Client is created using 83 // google.DefaultClient, which uses default credentials. 84 func New(proj string, cfg string, c *http.Client) (Config, error) { 85 svc, err := getService(c) 86 if err != nil { 87 return nil, err 88 } 89 // TODO: Consider checking for the configuration errors before returning. 90 return &impl{svc, proj, cfg}, nil 91 } 92 93 // This helper function is used to actually connect to an RTC client. 94 func getService(c *http.Client) (*runtimeconfig.ProjectsConfigsVariablesService, error) { 95 if c == nil { 96 var err error 97 c, err = google.DefaultClient(context.TODO(), runtimeconfig.CloudruntimeconfigScope) 98 if err != nil { 99 return nil, err 100 } 101 } 102 rtcService, err := runtimeconfig.New(c) 103 if err != nil { 104 return nil, err 105 } 106 return runtimeconfig.NewProjectsConfigsVariablesService(rtcService), nil 107 } 108 109 // GetProject will return the alphanumeric project ID for the configuration 110 func (s *impl) GetProject() string { 111 return s.proj 112 } 113 114 // Write will add or change key/val pair in the configuration for s's project 115 // using the RTC API. An empty key will return an error, however empty vals are 116 // perfectly fine, and will return a variable with an empty-string value. 117 func (s *impl) Write(key string, val []byte) error { 118 path := "projects/" + s.proj + "/configs/" + s.cfg 119 encoded := base64.StdEncoding.EncodeToString(val) 120 v := runtimeconfig.Variable{ 121 Name: path + "/variables/" + key, 122 Value: encoded, 123 // ForceSendFields will force value to be sent, even if empty. 124 ForceSendFields: []string{"value"}, 125 } 126 127 // Create. If error, check error type, and maybe update. 128 _, err := s.svc.Create(path, &v).Do() 129 if err != nil { 130 // If error is 'ALREADY_EXISTS', then we call update. 131 apierr, ok := err.(*googleapi.Error) 132 if ok && apierr.Message == "Requested entity already exists" { 133 path += "/variables/" + key 134 v.Name = "" 135 _, err = s.svc.Update(path, &v).Do() 136 if err != nil { 137 return fmt.Errorf("rtc.Write %#v : unable to update variable : %v", v, err) 138 } 139 } else { 140 return fmt.Errorf("rtc.Write %#v : unable to create variable : %v", v, err) 141 } 142 } 143 return err 144 } 145 146 // Delete removes a key/val pair from the configuration in s's project using 147 // the RTC API. An empty key will return an error. 148 func (s *impl) Delete(key string) error { 149 path := "projects/" + s.proj + "/configs/" + s.cfg + "/variables/" + key 150 _, err := s.svc.Delete(path).Do() 151 return err 152 } 153 154 // Val attempts to decode the value for a given Variable. 155 func (s *impl) Val(v *runtimeconfig.Variable) ([]byte, error) { 156 decoded, err := base64.StdEncoding.DecodeString(v.Value) 157 if err != nil { 158 return nil, fmt.Errorf("rtc.Val %#v : unable to decode value : %v", v, err) 159 } 160 return decoded, nil 161 } 162 163 // List provides a slice of all variables in the configuration for s's 164 // project using the RTC API. 165 func (s *impl) List() ([]*runtimeconfig.Variable, error) { 166 path := "projects/" + s.proj + "/configs/" + s.cfg 167 resp, err := s.svc.List(path).ReturnValues(true).Do() 168 if err != nil { 169 return nil, err 170 } 171 return resp.Variables, nil 172 } 173 174 // FilterList provides a slice of all variables in the configuration for s's 175 // project using the RTC API, filtered by the given filter string. More about this 176 // behavior can be found in the RTC documentation. 177 // https://cloud.google.com/deployment-manager/runtime-configurator/reference/rest/v1beta1/projects.configs.variables/list 178 func (s *impl) FilterList(filter string) ([]*runtimeconfig.Variable, error) { 179 path := "projects/" + s.proj + "/configs/" + s.cfg 180 resp, err := s.svc.List(path).Filter(filter).ReturnValues(true).Do() 181 if err != nil { 182 return nil, err 183 } 184 return resp.Variables, nil 185 }