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  }