Add Service Fabric Provider
This commit is contained in:
parent
9f6f637527
commit
39c1cc1b3c
21 changed files with 1624 additions and 18 deletions
367
vendor/github.com/jjcollinge/servicefabric/servicefabric.go
generated
vendored
Normal file
367
vendor/github.com/jjcollinge/servicefabric/servicefabric.go
generated
vendored
Normal file
|
@ -0,0 +1,367 @@
|
|||
// Package servicefabric is an opinionated Service Fabric client written in Golang
|
||||
package servicefabric
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const DefaultAPIVersion = "3.0"
|
||||
|
||||
// Client for Service Fabric.
|
||||
// This is purposely a subset of the total Service Fabric API surface.
|
||||
type Client struct {
|
||||
// endpoint Service Fabric cluster management endpoint
|
||||
endpoint string
|
||||
// apiVersion Service Fabric API version
|
||||
apiVersion string
|
||||
// httpClient HTTP client
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient returns a new provider client that can query the
|
||||
// Service Fabric management API externally or internally
|
||||
func NewClient(httpClient *http.Client, endpoint, apiVersion string, tlsConfig *tls.Config) (*Client, error) {
|
||||
if endpoint == "" {
|
||||
return nil, errors.New("endpoint missing for httpClient configuration")
|
||||
}
|
||||
if apiVersion == "" {
|
||||
apiVersion = DefaultAPIVersion
|
||||
}
|
||||
|
||||
if tlsConfig != nil {
|
||||
tlsConfig.Renegotiation = tls.RenegotiateFreelyAsClient
|
||||
tlsConfig.BuildNameToCertificate()
|
||||
httpClient.Transport = &http.Transport{TLSClientConfig: tlsConfig}
|
||||
}
|
||||
|
||||
return &Client{
|
||||
endpoint: endpoint,
|
||||
apiVersion: apiVersion,
|
||||
httpClient: httpClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetApplications returns all the registered applications
|
||||
// within the Service Fabric cluster.
|
||||
func (c Client) GetApplications() (*ApplicationItemsPage, error) {
|
||||
var aggregateAppItemsPages ApplicationItemsPage
|
||||
var continueToken string
|
||||
for {
|
||||
res, err := c.getHTTP("Applications/", withContinue(continueToken))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var appItemsPage ApplicationItemsPage
|
||||
err = json.Unmarshal(res, &appItemsPage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not deserialise JSON response: %+v", err)
|
||||
}
|
||||
|
||||
aggregateAppItemsPages.Items = append(aggregateAppItemsPages.Items, appItemsPage.Items...)
|
||||
|
||||
continueToken = getString(appItemsPage.ContinuationToken)
|
||||
if continueToken == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return &aggregateAppItemsPages, nil
|
||||
}
|
||||
|
||||
// GetServices returns all the services associated
|
||||
// with a Service Fabric application.
|
||||
func (c Client) GetServices(appName string) (*ServiceItemsPage, error) {
|
||||
var aggregateServiceItemsPages ServiceItemsPage
|
||||
var continueToken string
|
||||
for {
|
||||
res, err := c.getHTTP("Applications/"+appName+"/$/GetServices", withContinue(continueToken))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var servicesItemsPage ServiceItemsPage
|
||||
err = json.Unmarshal(res, &servicesItemsPage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not deserialise JSON response: %+v", err)
|
||||
}
|
||||
|
||||
aggregateServiceItemsPages.Items = append(aggregateServiceItemsPages.Items, servicesItemsPage.Items...)
|
||||
|
||||
continueToken = getString(servicesItemsPage.ContinuationToken)
|
||||
if continueToken == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return &aggregateServiceItemsPages, nil
|
||||
}
|
||||
|
||||
// GetPartitions returns all the partitions associated
|
||||
// with a Service Fabric service.
|
||||
func (c Client) GetPartitions(appName, serviceName string) (*PartitionItemsPage, error) {
|
||||
var aggregatePartitionItemsPages PartitionItemsPage
|
||||
var continueToken string
|
||||
for {
|
||||
basePath := "Applications/" + appName + "/$/GetServices/" + serviceName + "/$/GetPartitions/"
|
||||
res, err := c.getHTTP(basePath, withContinue(continueToken))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var partitionsItemsPage PartitionItemsPage
|
||||
err = json.Unmarshal(res, &partitionsItemsPage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not deserialise JSON response: %+v", err)
|
||||
}
|
||||
|
||||
aggregatePartitionItemsPages.Items = append(aggregatePartitionItemsPages.Items, partitionsItemsPage.Items...)
|
||||
|
||||
continueToken = getString(partitionsItemsPage.ContinuationToken)
|
||||
if continueToken == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return &aggregatePartitionItemsPages, nil
|
||||
}
|
||||
|
||||
// GetInstances returns all the instances associated
|
||||
// with a stateless Service Fabric partition.
|
||||
func (c Client) GetInstances(appName, serviceName, partitionName string) (*InstanceItemsPage, error) {
|
||||
var aggregateInstanceItemsPages InstanceItemsPage
|
||||
var continueToken string
|
||||
for {
|
||||
basePath := "Applications/" + appName + "/$/GetServices/" + serviceName + "/$/GetPartitions/" + partitionName + "/$/GetReplicas"
|
||||
res, err := c.getHTTP(basePath, withContinue(continueToken))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var instanceItemsPage InstanceItemsPage
|
||||
err = json.Unmarshal(res, &instanceItemsPage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not deserialise JSON response: %+v", err)
|
||||
}
|
||||
|
||||
aggregateInstanceItemsPages.Items = append(aggregateInstanceItemsPages.Items, instanceItemsPage.Items...)
|
||||
|
||||
continueToken = getString(instanceItemsPage.ContinuationToken)
|
||||
if continueToken == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return &aggregateInstanceItemsPages, nil
|
||||
}
|
||||
|
||||
// GetReplicas returns all the replicas associated
|
||||
// with a stateful Service Fabric partition.
|
||||
func (c Client) GetReplicas(appName, serviceName, partitionName string) (*ReplicaItemsPage, error) {
|
||||
var aggregateReplicaItemsPages ReplicaItemsPage
|
||||
var continueToken string
|
||||
for {
|
||||
basePath := "Applications/" + appName + "/$/GetServices/" + serviceName + "/$/GetPartitions/" + partitionName + "/$/GetReplicas"
|
||||
res, err := c.getHTTP(basePath, withContinue(continueToken))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var replicasItemsPage ReplicaItemsPage
|
||||
err = json.Unmarshal(res, &replicasItemsPage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not deserialise JSON response: %+v", err)
|
||||
}
|
||||
|
||||
aggregateReplicaItemsPages.Items = append(aggregateReplicaItemsPages.Items, replicasItemsPage.Items...)
|
||||
|
||||
continueToken = getString(replicasItemsPage.ContinuationToken)
|
||||
if continueToken == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return &aggregateReplicaItemsPages, nil
|
||||
}
|
||||
|
||||
// GetServiceExtension returns all the extensions specified
|
||||
// in a Service's manifest file. If the XML schema does not
|
||||
// map to the provided interface, the default type interface will
|
||||
// be returned.
|
||||
func (c Client) GetServiceExtension(appType, applicationVersion, serviceTypeName, extensionKey string, response interface{}) error {
|
||||
res, err := c.getHTTP("ApplicationTypes/"+appType+"/$/GetServiceTypes", withParam("ApplicationTypeVersion", applicationVersion))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error requesting service extensions: %v", err)
|
||||
}
|
||||
|
||||
var serviceTypes []ServiceType
|
||||
err = json.Unmarshal(res, &serviceTypes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not deserialise JSON response: %+v", err)
|
||||
}
|
||||
|
||||
for _, serviceTypeInfo := range serviceTypes {
|
||||
if serviceTypeInfo.ServiceTypeDescription.ServiceTypeName == serviceTypeName {
|
||||
for _, extension := range serviceTypeInfo.ServiceTypeDescription.Extensions {
|
||||
if strings.EqualFold(extension.Key, extensionKey) {
|
||||
err = xml.Unmarshal([]byte(extension.Value), &response)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not deserialise extension's XML value: %+v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetProperties uses the Property Manager API to retrieve
|
||||
// string properties from a name as a dictionary
|
||||
func (c Client) GetProperties(name string) (bool, map[string]string, error) {
|
||||
nameExists, err := c.nameExists(name)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if !nameExists {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
properties := make(map[string]string)
|
||||
|
||||
var continueToken string
|
||||
for {
|
||||
res, err := c.getHTTP("Names/"+name+"/$/GetProperties", withContinue(continueToken), withParam("IncludeValues", "true"))
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
var propertiesListPage PropertiesListPage
|
||||
err = json.Unmarshal(res, &propertiesListPage)
|
||||
if err != nil {
|
||||
return false, nil, fmt.Errorf("could not deserialise JSON response: %+v", err)
|
||||
}
|
||||
|
||||
for _, property := range propertiesListPage.Properties {
|
||||
if property.Value.Kind != "String" {
|
||||
continue
|
||||
}
|
||||
properties[property.Name] = property.Value.Data
|
||||
}
|
||||
|
||||
continueToken = propertiesListPage.ContinuationToken
|
||||
if continueToken == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return true, properties, nil
|
||||
}
|
||||
|
||||
// GetServiceLabels add labels from service manifest extensions and properties manager
|
||||
// expects extension xml in <Label key="key">value</Label>
|
||||
func (c Client) GetServiceLabels(service *ServiceItem, app *ApplicationItem, prefix string) (map[string]string, error) {
|
||||
extensionData := ServiceExtensionLabels{}
|
||||
err := c.GetServiceExtension(app.TypeName, app.TypeVersion, service.TypeName, prefix, &extensionData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prefixPeriod := prefix + "."
|
||||
|
||||
labels := map[string]string{}
|
||||
if extensionData.Label != nil {
|
||||
for _, label := range extensionData.Label {
|
||||
if strings.HasPrefix(label.Key, prefixPeriod) {
|
||||
labelKey := strings.Replace(label.Key, prefixPeriod, "", -1)
|
||||
labels[labelKey] = label.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exists, properties, err := c.GetProperties(service.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if exists {
|
||||
for k, v := range properties {
|
||||
if strings.HasPrefix(k, prefixPeriod) {
|
||||
labelKey := strings.Replace(k, prefixPeriod, "", -1)
|
||||
labels[labelKey] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
func (c Client) nameExists(propertyName string) (bool, error) {
|
||||
res, err := c.getHTTPRaw("Names/" + propertyName)
|
||||
// Get http will return error for any non 200 response code.
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return res.StatusCode == http.StatusOK, nil
|
||||
}
|
||||
|
||||
func (c Client) getHTTP(basePath string, paramsFuncs ...queryParamsFunc) ([]byte, error) {
|
||||
if c.httpClient == nil {
|
||||
return nil, errors.New("invalid http client provided")
|
||||
}
|
||||
|
||||
url := c.getURL(basePath, paramsFuncs...)
|
||||
res, err := c.httpClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to Service Fabric server %+v on %s", err, url)
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("Service Fabric responded with error code %s to request %s with body %v", res.Status, url, res.Body)
|
||||
}
|
||||
|
||||
if res.Body == nil {
|
||||
return nil, errors.New("empty response body from Service Fabric")
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
body, readErr := ioutil.ReadAll(res.Body)
|
||||
if readErr != nil {
|
||||
return nil, fmt.Errorf("failed to read response body from Service Fabric response %+v", readErr)
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func (c Client) getHTTPRaw(basePath string) (*http.Response, error) {
|
||||
if c.httpClient == nil {
|
||||
return nil, fmt.Errorf("invalid http client provided")
|
||||
}
|
||||
|
||||
url := c.getURL(basePath)
|
||||
|
||||
res, err := c.httpClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to Service Fabric server %+v on %s", err, url)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c Client) getURL(basePath string, paramsFuncs ...queryParamsFunc) string {
|
||||
params := []string{"api-version=" + c.apiVersion}
|
||||
|
||||
for _, paramsFunc := range paramsFuncs {
|
||||
params = paramsFunc(params)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s?%s", c.endpoint, basePath, strings.Join(params, "&"))
|
||||
}
|
||||
|
||||
func getString(str *string) string {
|
||||
if str == nil {
|
||||
return ""
|
||||
}
|
||||
return *str
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue